linux-brain/drivers/mxc/xuvi/ppm.c

1270 lines
28 KiB
C

/*
* Copyright 2019-2020 NXP
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
/*!
* @file ppm.c
*
* copyright here may be changed later
*
*
*/
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/of_irq.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/seq_file.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/videodev2.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>
#include <linux/file.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/platform_data/dma-imx.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/pm_runtime.h>
#include <linux/mx8_mu.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/bitops.h>
#include "ppm.h"
#include "xuvi_pm.h"
#include "xuvi_mu.h"
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
#define CSR_CM0Px_ADDR_OFFSET 0x00000000
#define CSR_CM0Px_CPUWAIT 0x00000004
#define LVL_DEBUG 3
#define LVL_INFO 2
#define LVL_WARN 1
#define LVL_ERR 0
static int ppm_debug_level = 1;
static int debug_firmware;
module_param(ppm_debug_level, int, 0644);
MODULE_PARM_DESC(ppm_debug_level, "ppm debug level (0-3)");
module_param(debug_firmware, int, 0644);
MODULE_PARM_DESC(debug_firmware, "open FW debug log");
#define dprintk(level, fmt, arg...) do { \
if (ppm_debug_level >= (level)) \
printk(KERN_ERR pr_fmt("level: %d %s line: %d: " fmt), \
level, __func__, __LINE__, ##arg); \
} while (0)
static void dvb_dmx_filter(struct dvb_demux *demux, const u8 *buf,
uint32_t len, u16 pid)
{
struct dvb_demux_feed *feed;
struct ppm_ctx *ctx = demux->priv;
struct ppm_dev *ppm = ctx->dev;
list_for_each_entry(feed, &demux->feed_list, list_head) {
if (ctx->stop_start || !feed)
break;
if ((feed->pid != pid) && (feed->pid != 0x2000))
continue;
while (feed->cb.ts(buf, len, NULL, 0, &feed->feed.ts,
&feed->buffer_flags) < 0) {
dprintk(LVL_DEBUG, "ringbuffer overflow, retry");
if (ctx->stop_start || ppm->suspend) {
dprintk(LVL_WARN, "suspend or stop drop");
break;
}
msleep(1000);
}
}
}
static void send_msg(struct ppm_dev *dev, uint32_t value0, uint32_t value1,
uint32_t value2, uint32_t value3)
{
xuvi_mu_send_msg(dev, value0, value1, value2, value3);
}
static void ppm_send_cmd(struct ppm_dev *dev, uint32_t idx, uint32_t cmdid,
uint32_t value1, uint32_t value2, uint32_t value3)
{
uint32_t cmdword;
cmdword = 0;
cmdword |= ((idx & 0x000000ff) << 16);
cmdword |= ((cmdid & 0x00003fff) << 0);
dprintk(LVL_DEBUG, "send idx: %d msg: %x 0x%x %d %d\n", idx,
cmdword, value1, value2, value3);
send_msg(dev, cmdword, value1, value2, value3);
}
static struct ppm_buf_list *find_buf(struct ppm_dev *ppm, uint32_t phy,
bool is_input)
{
int i;
struct ppm_buf_list *buf;
for (i = 0; i < BUF_NUM; i++) {
if (is_input)
buf = &ppm->bufs[i];
else
buf = &ppm->out_bufs[i];
if (buf->phy == phy)
return buf;
}
dprintk(LVL_ERR, "can't find buffer: 0x%x is_input %d\n", phy,
is_input);
return NULL;
}
static void ppm_event_handler(struct ppm_ctx *ctx, uint32_t idx,
uint32_t evt, uint32_t *event_data)
{
struct ppm_dev *dev;
struct ppm_buf_list *buf;
uint32_t phy = event_data[1];
uint32_t len = event_data[2];
uint32_t pid = event_data[3];
if (ctx == NULL) {
dprintk(LVL_ERR,
"receive event: 0x%X after instance released, ignore it\n",
evt);
return;
}
dev = ctx->dev;
switch (evt) {
case STOP:
dprintk(LVL_ERR, "receive PPM_EVENT_STOPPED\n");
ctx->fw_stopped = true;
complete(&ctx->stop_comp);
break;
case BUF_FROM:
if (len) {
buf = find_buf(dev, phy, false);
dvb_dmx_filter(&ctx->demux, buf->vir, len, pid);
mutex_lock(&dev->buf_list_lock);
ppm_send_cmd(dev, 0, BUF_TO, buf->phy, 0,
buf->size);
mutex_unlock(&dev->buf_list_lock);
} else {
buf = find_buf(dev, phy, true);
mutex_lock(&dev->buf_list_lock);
list_add_tail(&buf->list, &dev->buf_list);
mutex_unlock(&dev->buf_list_lock);
}
break;
default:
dprintk(LVL_ERR, "warning: evt %d is not handled\n", evt);
break;
}
dprintk(LVL_DEBUG, "leave, evt %d\n", evt);
}
static void send_msg_queue(struct ppm_ctx *ctx, struct event_msg *msg)
{
uint32_t ret;
ret =
kfifo_in(&ctx->msg_fifo, msg,
sizeof(uint32_t) * (MSG_WORD_LENGTH + msg->msgnum));
if (ret != sizeof(uint32_t) * (MSG_WORD_LENGTH + msg->msgnum))
dprintk(LVL_ERR,
"There is no memory for msg fifo, ret=%d\n", ret);
}
static bool receive_msg_queue(struct ppm_ctx *ctx, struct event_msg *msg)
{
uint32_t ret;
struct ppm_dev *ppm = ctx->dev;
if (ppm->suspend)
return false;
if (kfifo_len(&ctx->msg_fifo) >=
sizeof(uint32_t) * MSG_WORD_LENGTH) {
ret =
kfifo_out(&ctx->msg_fifo, msg,
sizeof(uint32_t) * MSG_WORD_LENGTH);
if (ret != sizeof(uint32_t) * MSG_WORD_LENGTH) {
dprintk(LVL_ERR,
"kfifo_out msg word has error, ret=%d\n",
ret);
return false;
} else {
if (msg->msgnum > 0) {
if (kfifo_len(&ctx->msg_fifo) >=
sizeof(uint32_t) * msg->msgnum) {
ret =
kfifo_out(&ctx->msg_fifo,
msg->msgdata,
sizeof(uint32_t) *
msg->msgnum);
if (ret !=
sizeof(uint32_t) *
msg->msgnum) {
dprintk(LVL_ERR,
"kfifo_out msg data has error, ret=%d\n",
ret);
return false;
} else
return true;
} else
return false;
} else
return true;
}
} else
return false;
}
static void read_msg(struct ppm_dev *dev, struct event_msg *msg)
{
uint32_t msg_mu[4];
uint32_t msgword;
uint32_t ret;
ret = kfifo_out(&dev->msg_fifo, msg_mu, sizeof(uint32_t) * 4);
if (ret != sizeof(uint32_t) * 4) {
dprintk(LVL_ERR, "kfifo_out msg word has error, ret=%d\n",
ret);
}
msgword = msg_mu[0];
msg->idx = ((msgword & 0x00ff0000) >> 16);
msg->msgid = ((msgword & 0x00003fff) >> 0);
msg->msgnum = 4;
msg->msgdata[0] = msg_mu[0];
msg->msgdata[1] = msg_mu[1];
msg->msgdata[2] = msg_mu[2];
msg->msgdata[3] = msg_mu[3];
}
static void ppm_msg_instance_work(struct work_struct *work)
{
struct ppm_ctx *ctx =
container_of(work, struct ppm_ctx, instance_work);
struct event_msg msg;
memset(&msg, 0, sizeof(struct event_msg));
while (receive_msg_queue(ctx, &msg))
ppm_event_handler(ctx, msg.idx, msg.msgid, msg.msgdata);
}
static int ensure_buf(struct ppm_dev *ppm, bool is_input)
{
uint32_t i;
struct ppm_buf_list *buf;
if (is_input)
buf = &ppm->bufs[0];
else
buf = &ppm->out_bufs[0];
if (buf->vir)
return 0;
for (i = 0; i < BUF_NUM; i++) {
if (is_input)
buf = &ppm->bufs[i];
else
buf = &ppm->out_bufs[i];
buf->vir = dma_alloc_coherent(&ppm->plat_dev->dev,
BUF_SIZE,
(dma_addr_t *) &buf->phy,
GFP_KERNEL | GFP_DMA32);
if (!buf->vir) {
dprintk(LVL_ERR, "dma buf alloc size(%d) fail!\n",
BUF_SIZE);
return -ENOMEM;
}
buf->size = BUF_SIZE;
if (is_input)
list_add_tail(&buf->list, &ppm->buf_list);
else
ppm_send_cmd(ppm, 0, BUF_TO, buf->phy, 0,
buf->size);
dprintk(LVL_DEBUG, "Allocate buffer: 0x%x 0x%p\n",
buf->phy, buf->vir);
}
return 0;
}
static int ppm_dvbdmx_write(struct dmx_demux *demux,
const char __user *buf_in, size_t count)
{
struct dvb_demux *dvbdemux = (struct dvb_demux *) demux;
struct ppm_ctx *ctx = dvbdemux->priv;
struct ppm_dev *dev = ctx->dev;
struct ppm_buf_list *buf;
int ret;
void *p;
if ((!demux->frontend)
|| (demux->frontend->source != DMX_MEMORY_FE))
return -EINVAL;
if (!count)
return 0;
p = memdup_user(buf_in, count);
if (IS_ERR(p))
return PTR_ERR(p);
ret = ensure_buf(dev, true);
if (ret) {
dprintk(LVL_ERR, "alloc ppm buffer fail\n");
return ret;
}
mutex_lock(&dev->dev_ins_mutex);
while (list_empty(&dev->buf_list)) {
if (signal_pending(current)) {
dprintk(LVL_WARN, "fatal_signal_pending");
mutex_unlock(&dev->dev_ins_mutex);
return 0;
}
msleep(20);
}
mutex_lock(&dev->buf_list_lock);
buf = list_first_entry(&dev->buf_list, struct ppm_buf_list, list);
list_del(&buf->list);
mutex_unlock(&dev->buf_list_lock);
mutex_unlock(&dev->dev_ins_mutex);
memcpy(buf->vir, p, count);
mutex_lock(&dev->buf_list_lock);
ppm_send_cmd(dev, ctx->idx, BUF_TO, buf->phy, count, buf->size);
mutex_unlock(&dev->buf_list_lock);
if (mutex_lock_interruptible(&dvbdemux->mutex)) {
kfree(p);
return -ERESTARTSYS;
}
kfree(p);
mutex_unlock(&dvbdemux->mutex);
return count;
}
static void ppm_run_fw(struct ppm_dev *dev)
{
writel(dev->fw_space_phy, dev->csr_base);
writel(0x0, dev->csr_base + CSR_CM0Px_CPUWAIT);
}
static int ppm_fw_download(struct ppm_dev *dev)
{
unsigned char *image;
unsigned int fw_size = 0;
int ret = 0;
char *p = dev->fw_space_vir;
ret = request_firmware((const struct firmware **) &dev->pfw,
M0FW_FILENAME, dev->generic_dev);
if (ret) {
dprintk(LVL_ERR, "request fw %s failed(%d)\n",
M0FW_FILENAME, ret);
return ret;
} else {
dprintk(LVL_INFO, "request fw %s got size(%d)\n",
M0FW_FILENAME, (int) dev->pfw->size);
image = (uint8_t *) dev->pfw->data;
fw_size = dev->pfw->size;
}
memcpy(dev->fw_space_vir, image, fw_size);
release_firmware(dev->pfw);
dev->pfw = NULL;
p[18] = 1;
ppm_run_fw(dev);
return ret;
}
static int ppm_dev_event_handler(struct ppm_dev *dev,
struct event_msg *msg)
{
int cmd;
cmd = msg->msgdata[0];
cmd &= 0x00003fff;
if (cmd == 0xaa) {
ppm_send_cmd(dev, 0, BOOT_ADDRESS, dev->fw_space_phy, 0,
0);
ppm_send_cmd(dev, 0, INIT_DONE, LOG_OFFSET,
dev->boot_size - LOG_OFFSET, 0);
dev->print_buf = dev->fw_space_vir + LOG_OFFSET;
} else if (cmd == 0x55) {
dev->fw_started = true;
complete(&dev->fw_start_comp);
} else if (cmd == 0xA5) {
complete(&dev->snap_done_comp);
} else if (cmd == HARD_FAULT) {
dprintk(LVL_ERR, "FW exception. lr: %x addr: %x\n",
msg->msgdata[1], msg->msgdata[2]);
} else {
return 1;
}
return 0;
}
static void ppm_msg_run_work(struct work_struct *work)
{
struct ppm_dev *dev = container_of(work, struct ppm_dev, msg_work);
struct ppm_ctx *ctx;
struct event_msg msg;
memset(&msg, 0, sizeof(struct event_msg));
while (kfifo_len(&dev->msg_fifo) >= sizeof(uint32_t) * 4) {
read_msg(dev, &msg);
if (ppm_dev_event_handler(dev, &msg) == 0)
continue;
mutex_lock(&dev->dev_mutex);
ctx = dev->ctx[msg.idx];
if (ctx != NULL) {
mutex_lock(&ctx->instance_mutex);
if (!ctx->ctx_released) {
send_msg_queue(ctx, &msg);
queue_work(ctx->instance_wq,
&ctx->instance_work);
}
mutex_unlock(&ctx->instance_mutex);
}
mutex_unlock(&dev->dev_mutex);
}
}
static void print_firmware_debug(char *ptr, u32 size)
{
u32 total = 0;
u32 len;
while (total < size) {
len = min_t(u32, size - total, 256);
dprintk(LVL_ERR, "%.*s", len, ptr + total);
total += len;
}
}
static void firmware_debug(struct ppm_dev *dev)
{
char *ptr;
u32 rptr;
u32 wptr;
if (!dev || !dev->print_buf)
return;
if (!debug_firmware)
return;
rptr = dev->print_buf->read;
wptr = dev->print_buf->write;
if (rptr == wptr)
return;
ptr = dev->print_buf->buffer;
if (rptr > wptr) {
print_firmware_debug(ptr + rptr,
dev->print_buf->bytes - rptr);
rptr = 0;
}
if (rptr < wptr) {
print_firmware_debug(ptr + rptr, wptr - rptr);
rptr = wptr;
}
if (rptr >= dev->print_buf->bytes)
rptr = 0;
dev->print_buf->read = rptr;
}
static void ppm_watchdog_handler(struct work_struct *work)
{
struct delayed_work *dwork;
struct ppm_dev *dev;
if (!work)
return;
dwork = to_delayed_work(work);
dev = container_of(dwork, struct ppm_dev, watchdog);
firmware_debug(dev);
schedule_delayed_work(&dev->watchdog,
msecs_to_jiffies(PPM_WATCHDOG_INTERVAL_MS));
}
static void init_ppm_watchdog(struct ppm_dev *dev)
{
if (!dev)
return;
INIT_DELAYED_WORK(&dev->watchdog, ppm_watchdog_handler);
schedule_delayed_work(&dev->watchdog,
msecs_to_jiffies(PPM_WATCHDOG_INTERVAL_MS));
}
static int ppm_start(struct ppm_ctx *ctx)
{
int ret = 0;
struct ppm_dev *dev = ctx->dev;
pm_runtime_get_sync(&dev->plat_dev->dev);
ctx->ctx_released = false;
ctx->fw_stopped = false;
ctx->stop_start = false;
if (!ctx->instance_activated) {
init_completion(&ctx->stop_comp);
init_completion(&ctx->eos_comp);
INIT_WORK(&ctx->instance_work, ppm_msg_instance_work);
mutex_init(&ctx->instance_mutex);
if (kfifo_alloc(&ctx->msg_fifo,
sizeof(struct event_msg) *
PPM_MESSAGE_LIMIT, GFP_KERNEL)) {
dprintk(LVL_ERR, "fail to alloc fifo when open\n");
ret = -ENOMEM;
goto err_alloc_fifo;
}
ctx->instance_wq =
alloc_workqueue("ppm_instance",
WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
if (!ctx->instance_wq) {
dprintk(LVL_ERR,
"unable to alloc workqueue for ctx\n");
ret = -ENOMEM;
goto err_alloc_wq;
}
ctx->instance_activated = true;
}
mutex_lock(&dev->dev_mutex);
if (!dev->fw_is_ready) {
dev->fw_space_vir =
ioremap_wc(dev->fw_space_phy, dev->boot_size);
if (!dev->fw_space_vir) {
dprintk(LVL_ERR, "can't map firmware space\n");
ret = -1;
mutex_unlock(&dev->dev_mutex);
goto err_fw_load;
}
memset_io(dev->fw_space_vir, 0, dev->boot_size);
ret = ppm_fw_download(dev);
if (ret) {
dprintk(LVL_ERR, "ppm_fw_download fail\n");
mutex_unlock(&dev->dev_mutex);
goto err_fw_load;
}
dprintk(LVL_INFO, "done: ppm_fw_download\n");
if (!ctx->dev->fw_started)
if (!wait_for_completion_timeout
(&ctx->dev->fw_start_comp,
msecs_to_jiffies(10000))) {
dprintk(LVL_ERR,
"don't get start interrupt\n");
ret = -1;
mutex_unlock(&dev->dev_mutex);
goto err_fw_load;
}
dev->fw_is_ready = true;
}
ret = ensure_buf(dev, false);
if (ret) {
dprintk(LVL_ERR, "alloc ppm buffer fail\n");
mutex_unlock(&dev->dev_mutex);
goto err_fw_load;
}
mutex_unlock(&dev->dev_mutex);
return 0;
err_fw_load:
destroy_workqueue(ctx->instance_wq);
err_alloc_wq:
if (&ctx->msg_fifo)
kfifo_free(&ctx->msg_fifo);
err_alloc_fifo:
pm_runtime_put_sync(dev->generic_dev);
return ret;
}
static int ppm_stop(struct ppm_ctx *ctx)
{
struct ppm_dev *dev = ctx->dev;
dprintk(LVL_ERR, "ppm stop");
if (!ctx->fw_stopped && ctx->start_flag == false) {
dprintk(LVL_INFO, "send STOP\n");
ppm_send_cmd(dev, ctx->idx, STOP, 0, 0, 0);
if (!wait_for_completion_timeout
(&ctx->stop_comp, msecs_to_jiffies(1000))) {
dprintk(LVL_ERR,
"the path id:%d fw hang after send PPM_CMD_STOP\n",
ctx->idx);
}
} else
dprintk(LVL_INFO, "stopped(%d): skip PPM_CMD_STOP\n",
ctx->fw_stopped);
pm_runtime_put_sync(dev->generic_dev);
return 0;
}
static int ppm_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
{
struct dvb_demux *dvbdmx = dvbdmxfeed->demux;
struct ppm_ctx *ppm = dvbdmx->priv;
struct dvb_demux_feed *feed;
dprintk(LVL_DEBUG, "PPM DVB Start feed");
if (!dvbdmx->dmx.frontend) {
dprintk(LVL_DEBUG, "no frontend ?");
return -EINVAL;
}
ppm->feeds++;
dprintk(LVL_DEBUG, "ppm start feed, feeds=%d", ppm->feeds);
if (ppm->feeds == 1) {
dprintk(LVL_DEBUG, "ppm start feed & dma");
ppm_start(ppm);
}
list_for_each_entry(feed, &dvbdmx->feed_list, list_head) {
dprintk(LVL_DEBUG, "feed pid: 0x%X\n", feed->pid);
ppm_send_cmd(ppm->dev, 0, ADD_PID, feed->pid, 0, 0);
}
return ppm->feeds;
}
static int ppm_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
{
struct dvb_demux *dvbdmx = dvbdmxfeed->demux;
struct ppm_ctx *ctx = dvbdmx->priv;
struct dvb_demux_feed *feed;
dprintk(LVL_DEBUG, "PPM DVB Stop feed");
if (!dvbdmx->dmx.frontend) {
dprintk(LVL_DEBUG, "no frontend ?");
return -EINVAL;
}
ctx->feeds--;
ctx->stop_start = true;
if (ctx->feeds == 0) {
dprintk(LVL_DEBUG, "ppm stop feed and dma");
ppm_stop(ctx);
}
list_for_each_entry(feed, &dvbdmx->feed_list, list_head) {
dprintk(LVL_DEBUG, "feed pid: 0x%X\n", feed->pid);
ppm_send_cmd(ctx->dev, 0, RM_PID, feed->pid, 0, 0);
}
return 0;
}
int ppm_dvb_init(struct ppm_ctx *ppm)
{
int result = -1;
dprintk(LVL_DEBUG, "dvb_register_adapter");
result = dvb_register_adapter(&ppm->dvb_adapter,
"PPM DVB adapter",
THIS_MODULE,
&ppm->dev->plat_dev->dev,
adapter_nr);
if (result < 0) {
dprintk(LVL_ERR, "Error registering adapter");
return -ENODEV;
}
ppm->dvb_adapter.priv = ppm;
ppm->demux.dmx.capabilities = DMX_TS_FILTERING |
DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING;
ppm->demux.priv = ppm;
ppm->demux.filternum = 256;
ppm->demux.feednum = 256;
ppm->demux.start_feed = ppm_dvb_start_feed;
ppm->demux.stop_feed = ppm_dvb_stop_feed;
ppm->demux.write_to_decoder = NULL;
dprintk(LVL_DEBUG, "dvb_dmx_init");
result = dvb_dmx_init(&ppm->demux);
if (result < 0) {
dprintk(LVL_ERR, "dvb_dmx_init failed, ERROR=%d", result);
goto err0;
}
ppm->dmxdev.filternum = 256;
ppm->dmxdev.demux = &ppm->demux.dmx;
ppm->dmxdev.demux->write = ppm_dvbdmx_write;
ppm->dmxdev.capabilities = 0;
dprintk(LVL_DEBUG, "dvb_dmxdev_init");
result = dvb_dmxdev_init(&ppm->dmxdev, &ppm->dvb_adapter);
if (result < 0) {
dprintk(LVL_ERR, "dvb_dmxdev_init failed, ERROR=%d",
result);
goto err1;
}
ppm->fe_hw.source = DMX_FRONTEND_0;
result = ppm->demux.dmx.add_frontend(&ppm->demux.dmx, &ppm->fe_hw);
if (result < 0) {
dprintk(LVL_ERR, "dvb_dmx_init failed, ERROR=%d", result);
goto err2;
}
ppm->fe_mem.source = DMX_MEMORY_FE;
result =
ppm->demux.dmx.add_frontend(&ppm->demux.dmx, &ppm->fe_mem);
if (result < 0) {
dprintk(LVL_ERR, "dvb_dmx_init failed, ERROR=%d", result);
goto err3;
}
result =
ppm->demux.dmx.connect_frontend(&ppm->demux.dmx, &ppm->fe_hw);
if (result < 0) {
dprintk(LVL_ERR, "dvb_dmx_init failed, ERROR=%d", result);
goto err4;
}
dvb_net_init(&ppm->dvb_adapter, &ppm->dvbnet, &ppm->demux.dmx);
return 0;
/* Error conditions .. */
err4:
ppm->demux.dmx.remove_frontend(&ppm->demux.dmx, &ppm->fe_mem);
err3:
ppm->demux.dmx.remove_frontend(&ppm->demux.dmx, &ppm->fe_hw);
err2:
dvb_dmxdev_release(&ppm->dmxdev);
err1:
dvb_dmx_release(&ppm->demux);
err0:
dvb_unregister_adapter(&ppm->dvb_adapter);
return result;
}
static int ppm_dvb_exit(struct ppm_ctx *ppm)
{
dvb_net_release(&ppm->dvbnet);
ppm->demux.dmx.remove_frontend(&ppm->demux.dmx, &ppm->fe_mem);
ppm->demux.dmx.remove_frontend(&ppm->demux.dmx, &ppm->fe_hw);
dvb_dmxdev_release(&ppm->dmxdev);
dvb_dmx_release(&ppm->demux);
dprintk(LVL_DEBUG, "dvb_unregister_adapter");
dvb_unregister_adapter(&ppm->dvb_adapter);
return 0;
}
static int parse_dt_info(struct ppm_dev *dev, struct device_node *np)
{
struct resource reserved_res;
struct device_node *reserved_node;
uint32_t csr_base;
int ret;
if (!dev || !np)
return -EINVAL;
reserved_node = of_parse_phandle(np, "boot-region", 0);
if (!reserved_node) {
dprintk(LVL_ERR, "boot-region of_parse_phandle error\n");
return -ENODEV;
}
if (of_address_to_resource(reserved_node, 0, &reserved_res)) {
dprintk(LVL_ERR,
"boot-region of_address_to_resource error\n");
return -EINVAL;
}
dev->fw_space_phy = reserved_res.start;
dev->boot_size = resource_size(&reserved_res);
dprintk(LVL_DEBUG, "boot-region: %x size: %d\n", dev->fw_space_phy,
dev->boot_size);
ret = of_property_read_u32(np, "reg-csr", &csr_base);
if (ret) {
dprintk(LVL_ERR, "Cannot get csr offset %d\n", ret);
return -EINVAL;
}
dev->csr_base = ioremap(csr_base, 8);
return 0;
}
static int ppm_probe(struct platform_device *pdev)
{
struct ppm_dev *dev;
struct ppm_ctx *ctx;
struct device_node *np = pdev->dev.of_node;
int ret, i;
dprintk(LVL_DEBUG, "enter probe\n");
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->plat_dev = pdev;
ret = xuvi_attach_pm_domains(dev);
if (ret)
goto err_pm;
ret = parse_dt_info(dev, np);
if (ret) {
dprintk(LVL_ERR, "parse device tree fail\n");
goto err_put_dev;
}
for (i = 0; i < PPM_MAX_INSTANCE; i++) {
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
ret = -ENOMEM;
goto err_put_dev;
}
ctx->dev = dev;
ctx->idx = i;
dev->ctx[i] = ctx;
ret = ppm_dvb_init(ctx);
if (ret) {
dprintk(LVL_ERR, "ppm dvb init fail\n");
goto err_put_dev;
}
}
platform_set_drvdata(pdev, dev);
init_ppm_watchdog(dev);
mutex_init(&dev->dev_mutex);
mutex_init(&dev->dev_ins_mutex);
INIT_WORK(&dev->msg_work, ppm_msg_run_work);
INIT_LIST_HEAD(&dev->buf_list);
mutex_init(&dev->buf_list_lock);
init_waitqueue_head(&dev->buffer_wq);
init_completion(&dev->fw_start_comp);
init_completion(&dev->snap_done_comp);
if (kfifo_alloc(&dev->msg_fifo,
4 * 4 * PPM_MESSAGE_LIMIT, GFP_KERNEL)) {
dprintk(LVL_ERR, "fail to alloc fifo when open\n");
ret = -ENOMEM;
goto err_put_dev;
}
dev->workqueue =
alloc_workqueue("ppm", WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
if (!dev->workqueue) {
dprintk(LVL_ERR, "unable to alloc workqueue for dev\n");
ret = -ENOMEM;
goto err_put_dev;
}
dev->fw_started = false;
dev->fw_is_ready = false;
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
dev->generic_dev = get_device(&pdev->dev);
pm_runtime_put_sync(&pdev->dev);
return 0;
err_put_dev:
xuvi_detach_pm_domains(dev);
err_pm:
if (&dev->msg_fifo)
kfifo_free(&dev->msg_fifo);
if (dev->generic_dev) {
put_device(dev->generic_dev);
dev->generic_dev = NULL;
}
devm_kfree(&pdev->dev, dev);
dprintk(LVL_ERR, "probe failed\n");
return ret;
}
static void free_dma_buf(struct ppm_dev *dev)
{
struct ppm_buf_list *buf;
uint32_t i;
for (i = 0; i < BUF_NUM; i++) {
buf = &dev->out_bufs[i];
if (!buf->vir)
break;
dma_free_coherent(&dev->plat_dev->dev, buf->size, buf->vir,
buf->phy);
buf->vir = NULL;
}
for (i = 0; i < BUF_NUM; i++) {
buf = &dev->bufs[i];
if (!buf->vir)
break;
dma_free_coherent(&dev->plat_dev->dev, buf->size, buf->vir,
buf->phy);
buf->vir = NULL;
}
}
static int ppm_remove(struct platform_device *pdev)
{
struct ppm_dev *dev = platform_get_drvdata(pdev);
struct ppm_ctx *ctx;
int i;
dev->fw_started = false;
dev->fw_is_ready = false;
cancel_delayed_work_sync(&dev->watchdog);
for (i = 0; i < PPM_MAX_INSTANCE; i++) {
ctx = dev->ctx[i];
if (!ctx->instance_activated)
continue;
mutex_lock(&ctx->instance_mutex);
ctx->ctx_released = true;
kfifo_free(&ctx->msg_fifo);
destroy_workqueue(ctx->instance_wq);
mutex_unlock(&ctx->instance_mutex);
}
free_dma_buf(dev);
if (dev->fw_space_vir)
iounmap(dev->fw_space_vir);
dev->fw_space_vir = NULL;
dev->fw_space_phy = 0;
for (i = 0; i < PPM_MAX_INSTANCE; i++) {
ppm_dvb_exit(dev->ctx[i]);
kfree(dev->ctx[i]);
dev->ctx[i] = NULL;
}
if (&dev->msg_fifo)
kfifo_free(&dev->msg_fifo);
destroy_workqueue(dev->workqueue);
pm_runtime_disable(&pdev->dev);
xuvi_detach_pm_domains(dev);
if (dev->generic_dev) {
put_device(dev->generic_dev);
dev->generic_dev = NULL;
}
devm_kfree(&pdev->dev, dev);
return 0;
}
static int ppm_runtime_suspend(struct device *dev)
{
struct ppm_dev *ppmdev = (struct ppm_dev *)dev_get_drvdata(dev);
dprintk(LVL_DEBUG, "ppm_runtime_suspend\n");
if (ppmdev->generic_dev)
xuvi_mu_free(ppmdev);
return 0;
}
static int ppm_runtime_resume(struct device *dev)
{
int ret = 0;
struct ppm_dev *ppmdev = (struct ppm_dev *)dev_get_drvdata(dev);
dprintk(LVL_DEBUG, "ppm_runtime_resume\n");
if (ppmdev->generic_dev)
ret = xuvi_mu_request(ppmdev);
return ret;
}
static int find_first_available_instance(struct ppm_dev *dev)
{
int strIdx = 0;
if (!dev)
return -EINVAL;
return strIdx;
}
static void ppm_send_snapshot(struct ppm_dev *dev)
{
int strIdx;
strIdx = find_first_available_instance(dev);
if (strIdx >= 0 && strIdx < PPM_MAX_INSTANCE) {
dprintk(LVL_INFO, "send SNAP_SHOT\n");
ppm_send_cmd(dev, strIdx, SNAP_SHOT, 0, 0, 0);
} else
dprintk(LVL_WARN,
"warning: all path hang, need to reset\n");
}
static void ppm_dec_cancel_work(struct ppm_dev *ppmdev)
{
int i;
mutex_lock(&ppmdev->dev_mutex);
cancel_work_sync(&ppmdev->msg_work);
for (i = 0; i < PPM_MAX_INSTANCE; i++) {
struct ppm_ctx *ctx = ppmdev->ctx[i];
if (!ctx->instance_activated)
continue;
cancel_work_sync(&ppmdev->ctx[i]->instance_work);
}
cancel_delayed_work_sync(&ppmdev->watchdog);
mutex_unlock(&ppmdev->dev_mutex);
}
static bool ppm_has_instance(struct ppm_dev *ppmdev)
{
int i;
for (i = 0; i < PPM_MAX_INSTANCE; i++) {
struct ppm_ctx *ctx = ppmdev->ctx[i];
if (ctx)
return true;
}
return false;
}
static void ppm_dec_resume_work(struct ppm_dev *ppmdev)
{
int i;
mutex_lock(&ppmdev->dev_mutex);
queue_work(ppmdev->workqueue, &ppmdev->msg_work);
for (i = 0; i < PPM_MAX_INSTANCE; i++) {
struct ppm_ctx *ctx = ppmdev->ctx[i];
if (!ctx->instance_activated)
continue;
if (!ctx->ctx_released)
queue_work(ctx->instance_wq, &ctx->instance_work);
}
schedule_delayed_work(&ppmdev->watchdog,
msecs_to_jiffies(PPM_WATCHDOG_INTERVAL_MS));
mutex_unlock(&ppmdev->dev_mutex);
}
static int __maybe_unused ppm_suspend(struct device *dev)
{
struct ppm_dev *ppmdev = (struct ppm_dev *) dev_get_drvdata(dev);
int ret = 0;
dprintk(LVL_INFO, "suspend\n");
pm_runtime_get_sync(ppmdev->generic_dev);
if (ppmdev->fw_is_ready) {
ppm_send_snapshot(ppmdev);
reinit_completion(&ppmdev->snap_done_comp);
if (!wait_for_completion_timeout
(&ppmdev->snap_done_comp, msecs_to_jiffies(1000))) {
dprintk(LVL_ERR,
"error: wait for ppm decoder snapdone event timeout!\n");
ret = -1;
}
}
ppmdev->suspend = true;
ppm_dec_cancel_work(ppmdev);
ppmdev->suspend = false;
dprintk(LVL_INFO, "suspend done\n");
pm_runtime_put_sync(ppmdev->generic_dev);
return ret;
}
static bool is_ppm_poweroff(struct ppm_dev *ppmdev)
{
if (!ppmdev)
return false;
if (readl_relaxed(ppmdev->csr_base + CSR_CM0Px_CPUWAIT) == 1)
return true;
else
return false;
}
static int resume_from_snapshot(struct ppm_dev *ppmdev)
{
int ret = 0;
ppm_run_fw(ppmdev);
/*wait for firmware resotre done */
reinit_completion(&ppmdev->fw_start_comp);
if (!wait_for_completion_timeout
(&ppmdev->fw_start_comp, msecs_to_jiffies(1000))) {
dprintk(LVL_ERR,
"error: wait for ppm decoder resume done timeout!\n");
ret = -1;
}
return ret;
}
static int resume_from_ppm_poweroff(struct ppm_dev *ppmdev)
{
int ret = 0;
dprintk(LVL_INFO, "resume from poweroff\n");
ret = resume_from_snapshot(ppmdev);
return ret;
}
static int __maybe_unused ppm_resume(struct device *dev)
{
struct ppm_dev *ppmdev = (struct ppm_dev *) dev_get_drvdata(dev);
int ret = 0;
dprintk(LVL_INFO, "resume\n");
pm_runtime_get_sync(ppmdev->generic_dev);
if (ppmdev->fw_is_ready == false)
goto exit;
if (is_ppm_poweroff(ppmdev))
ret = resume_from_ppm_poweroff(ppmdev);
else if (ppm_has_instance(ppmdev))
dprintk(LVL_INFO, "resume from instance\n");
ppm_dec_resume_work(ppmdev);
exit:
pm_runtime_put_sync(ppmdev->generic_dev);
dprintk(LVL_INFO, "resume done\n");
return ret;
}
static const struct dev_pm_ops ppm_pm_ops = {
SET_RUNTIME_PM_OPS(ppm_runtime_suspend, ppm_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(ppm_suspend, ppm_resume)
};
static const struct of_device_id demux_device_match[] = {
{.compatible = "nxp,imx8qm-b0-vpu-ts",},
{},
};
MODULE_DEVICE_TABLE(of, demux_device_match);
static struct platform_driver demux_platform_driver = {
.probe = ppm_probe,
.remove = ppm_remove,
.driver = {
.pm = &ppm_pm_ops,
.name = "ppm",
.of_match_table = demux_device_match,
.owner = THIS_MODULE,
},
};
static int ppm_device_init(void)
{
return platform_driver_register(&demux_platform_driver);
}
static void ppm_device_exit(void)
{
platform_driver_unregister(&demux_platform_driver);
}
module_init(ppm_device_init)
module_exit(ppm_device_exit)
MODULE_AUTHOR("NXP");
MODULE_DESCRIPTION("i.MX8QM PPM driver");
MODULE_LICENSE("GPL");