sdk-hwV1.3/lichee/linux-4.9/drivers/char/sunxi-di/drv_div3x/di_driver.c

1003 lines
24 KiB
C

/*
* Copyright (c) 2007-2018 Allwinnertech Co., Ltd.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "di_driver.h"
#include "di_dev.h"
#include "di_fops.h"
#include "di_utils.h"
#include "di_debug.h"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#define DI_MODULE_NAME "deinterlace"
#define TAG "[DI]"
#define DI_VERSION_MAJOR 1
#define DI_VERSION_MINOR 0
#define DI_VERSION_PATCHLEVEL 0
static struct di_driver_data *di_drvdata;
static unsigned int di_debug_mode;
unsigned int di_device_get_debug_mode(void)
{
return di_debug_mode;
}
int di_drv_get_version(struct di_version *version)
{
if (version) {
version->version_major = DI_VERSION_MAJOR;
version->version_minor = DI_VERSION_MINOR;
version->version_patchlevel = DI_VERSION_PATCHLEVEL;
version->ip_version = di_dev_get_ip_version();
return 0;
}
return -EINVAL;
}
static ssize_t di_device_debug_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
ssize_t n = 0;
n += sprintf(buf + n, "1:enable debug mode 0:disable debug mode\n");
n += sprintf(buf + n, "current debug_mode:%d\n", di_debug_mode);
return n;
}
static ssize_t di_device_debug_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
char *end;
di_debug_mode = (unsigned int)simple_strtoull(buf, &end, 0);
return count;
}
static DEVICE_ATTR(debug_mode, S_IWUSR | S_IRUGO,
di_device_debug_mode_show, di_device_debug_mode_store);
static ssize_t
di_device_debug_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t n = 0;
n += sprintf(buf + n,
"echo [level] > /sys/class/deinterlace/deinterlace/debug\n");
n += sprintf(buf + n, "level 0: disable all kinds of di logs\n");
n += sprintf(buf + n, "level 1: enable error di logs\n");
n += sprintf(buf + n, "level 2: enable info di logs\n");
n += sprintf(buf + n, "level 3: enable debug di logs\n");
n += sprintf(buf + n, "level 4: enable debug di time detect logs\n");
n += sprintf(buf + n, "level 5: enable film mode detect logs\n");
n += sprintf(buf + n, "\nNow the debug level is:%d\n", debug_mask);
return n;
}
static ssize_t
di_device_debug_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int val;
char *end;
retval = count;
val = simple_strtoull(buf, &end, 0);
if (val < DEBUG_LEVEL_MAX) {
debug_mask = val;
} else {
pr_err("ERROR: invalid input log level:%u\n", val);
retval = -EINVAL;
}
return retval;
}
static DEVICE_ATTR(debug, S_IWUSR | S_IRUGO,
di_device_debug_show, di_device_debug_store);
static ssize_t
di_device_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct di_driver_data *data = di_drvdata;
ssize_t n = 0;
n += sprintf(buf + n, "DI Current Info:\n");
n += sprintf(buf + n, "irq_no:%u\n", data->irq_no);
n += sprintf(buf + n, "dev enable:%u pm_state:%s\n",
data->dev_enable, data->pm_state ? "suspend" : "resume");
n += sprintf(buf + n, "need_apply_fixed_para:%u\n",
data->need_apply_fixed_para);
n += sprintf(buf + n, "driver state:%s\n",
data->state ? "busy" : "idle");
return n;
}
static DEVICE_ATTR(info, S_IWUSR | S_IRUGO,
di_device_info_show, NULL);
static char debug_client_name[30];
static ssize_t di_device_client_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t n = 0;
struct di_client *client;
bool find = false;
struct di_driver_data *drvdata = di_drvdata;
n += sprintf(buf + n, "All of the di clients name:\n");
list_for_each_entry(client, &drvdata->clients, node)
n += sprintf(buf + n, "%s\n", client->name);
n += sprintf(buf + n, "\n\n");
list_for_each_entry(client, &drvdata->clients, node) {
if (!strcmp(client->name, debug_client_name)) {
find = true;
break;
}
}
if (!find) {
n += sprintf(buf + n, "Wrong debug_client_name:%s, please ",
debug_client_name);
n += sprintf(buf + n,
"echo [client_name] > /sys/class/deinterlace/deinterlace/client\n");
return n;
}
n += sprintf(buf + n, "debug_client_name:%s\n",
debug_client_name);
return n;
}
static ssize_t
di_device_client_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct di_client *client;
bool find = false;
struct di_driver_data *drvdata = di_drvdata;
char name[30] = { 0 };
memcpy(name, buf, count);
name[count - 1] = '\0';
list_for_each_entry(client, &drvdata->clients, node) {
if (!strcmp(name, client->name)) {
find = true;
break;
}
}
if (!find) {
DI_ERR("ERROR client name input:%s\n", buf);
return count;
}
strcpy(debug_client_name, client->name);
pr_info("set the debug client name:%s\n", debug_client_name);
return count;
}
static DEVICE_ATTR(client, S_IWUSR | S_IRUGO,
di_device_client_show, di_device_client_store);
static ssize_t di_device_timeout_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t n = 0;
struct di_client *client;
bool find = false;
struct di_driver_data *drvdata = di_drvdata;
n += sprintf(buf + n, "debug_client_name:%s\n", debug_client_name);
list_for_each_entry(client, &drvdata->clients, node) {
if (!strcmp(client->name, debug_client_name)) {
find = true;
break;
}
}
if (!find) {
n += sprintf(buf + n, "Wrong debug_client_name:%s, please ",
debug_client_name);
n += sprintf(buf + n,
"echo [client_name] > /sys/class/deinterlace/deinterlace/client\n");
return n;
}
n += sprintf(buf + n, "wait4start:%lld wait4finish:%lld\n",
client->timeout.wait4start, client->timeout.wait4finish);
return n;
}
static ssize_t
di_device_timeout_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
ssize_t n = 0;
unsigned long long start, finish;
char *end;
struct di_client *client;
bool find = false;
struct di_driver_data *drvdata = di_drvdata;
pr_info("debug_client_name:%s\n", debug_client_name);
list_for_each_entry(client, &drvdata->clients, node) {
if (!strcmp(client->name, debug_client_name)) {
find = true;
break;
}
}
if (!find) {
pr_info("Wrong debug_client_name:%s, please ", debug_client_name);
pr_info("echo [client_name] > /sys/class/deinterlace/deinterlace/client\n");
return n;
}
start = simple_strtoull(buf, &end, 0);
pr_info("set timeout, wait2start:%lld\n", start);
if ((*end != ' ') && (*end != ',')) {
pr_err("error separator:%c\n", *end);
return count;
}
finish = simple_strtoull(end + 1, &end, 0);
pr_info("set timeout, wait2finish:%lld\n", finish);
client->timeout.wait4start = start;
client->timeout.wait4finish = finish;
pr_info("set timeout wait4start:%lld wait4finish:%lld\n",
client->timeout.wait4start, client->timeout.wait4finish);
return count;
}
static DEVICE_ATTR(timeout, S_IWUSR | S_IRUGO,
di_device_timeout_show, di_device_timeout_store);
static ssize_t di_device_tnrmode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t n = 0;
struct di_client *client;
bool find = false;
struct di_driver_data *drvdata = di_drvdata;
n += sprintf(buf + n, "debug_client_name:%s\n", debug_client_name);
list_for_each_entry(client, &drvdata->clients, node) {
if (!strcmp(client->name, debug_client_name)) {
find = true;
break;
}
}
if (!find) {
n += sprintf(buf + n, "Wrong debug_client_name:%s, please ",
debug_client_name);
n += sprintf(buf + n,
"echo [client_name] > /sys/class/deinterlace/deinterlace/client\n");
return n;
}
n += sprintf(buf + n, "TNR mode:%d level:%d\n",
client->tnr_mode.mode, client->tnr_mode.level);
return n;
}
static ssize_t
di_device_tnrmode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
ssize_t n = 0;
unsigned int mode, level;
char *end;
struct di_client *client;
bool find = false;
struct di_driver_data *drvdata = di_drvdata;
pr_info("debug_client_name:%s\n", debug_client_name);
list_for_each_entry(client, &drvdata->clients, node) {
if (!strcmp(client->name, debug_client_name)) {
find = true;
break;
}
}
if (!find) {
pr_info("Wrong debug_client_name:%s, please ", debug_client_name);
pr_info("echo [client_name] > /sys/class/deinterlace/deinterlace/client\n");
return n;
}
mode = (unsigned int)simple_strtoull(buf, &end, 0);
if ((*end != ' ') && (*end != ',')) {
pr_err("error separator:%c\n", *end);
return count;
}
level = (unsigned int)simple_strtoull(end + 1, &end, 0);
client->tnr_mode.mode = mode;
client->tnr_mode.level = level;
pr_info("TNR mode:%d level:%d\n",
client->tnr_mode.mode, client->tnr_mode.level);
return count;
}
static DEVICE_ATTR(tnrmode, S_IWUSR | S_IRUGO,
di_device_tnrmode_show, di_device_tnrmode_store);
static struct attribute *di_device_attrs[] = {
&dev_attr_debug_mode.attr,
&dev_attr_debug.attr,
&dev_attr_info.attr,
&dev_attr_client.attr,
&dev_attr_timeout.attr,
&dev_attr_tnrmode.attr,
NULL
};
static struct attribute_group di_device_attr_group = {
.attrs = di_device_attrs,
};
static const struct attribute_group *di_device_attr_groups[] = {
&di_device_attr_group,
NULL
};
static int di_init_hw(struct di_driver_data *drvdata)
{
di_dev_set_reg_base(drvdata->reg_base);
return 0;
}
static int di_clk_enable(struct di_driver_data *drvdata)
{
if (!IS_ERR_OR_NULL(drvdata->iclk)) {
int ret = clk_prepare_enable(drvdata->iclk);
if (ret) {
DI_ERR(TAG"try to enable di clk failed!\n");
return ret;
}
} else {
DI_INFO(TAG"di clk handle is invalid for enable\n");
}
return 0;
}
static int di_clk_disable(struct di_driver_data *drvdata)
{
if (!IS_ERR_OR_NULL(drvdata->iclk))
clk_disable_unprepare(drvdata->iclk);
else
DI_INFO(TAG"di clk handle is invalid!\n");
return 0;
}
static int di_check_enable_device_locked(
struct di_driver_data *drvdata)
{
int ret = 0;
DI_DEBUG(TAG"client_cnt=%d, pm_state=%d, dev_en=%d\n",
drvdata->client_cnt, drvdata->pm_state, drvdata->dev_enable);
if (drvdata->pm_state == DI_PM_STATE_SUSPEND)
return 0;
if ((drvdata->client_cnt > 0)
&& (drvdata->dev_enable == false)) {
ret = di_clk_enable(drvdata);
if (ret)
return ret;
drvdata->dev_enable = true;
di_dev_enable_irq(DI_IRQ_FLAG_PROC_FINISH, 1);
} else if ((drvdata->client_cnt == 0)
&& (drvdata->dev_enable == true)) {
di_dev_enable_irq(DI_IRQ_FLAG_PROC_FINISH, 0);
ret = di_clk_disable(drvdata);
if (ret)
return ret;
drvdata->dev_enable = false;
} else if (drvdata->client_cnt < 0) {
DI_ERR(TAG"err client_cnt=%d\n", drvdata->client_cnt);
return -EINVAL;
}
return 0;
}
bool di_drv_is_valid_client(struct di_client *c)
{
struct di_driver_data *drvdata = di_drvdata;
struct di_client *client;
bool valid = false;
if (c) {
mutex_lock(&drvdata->mlock);
list_for_each_entry(client, &drvdata->clients, node) {
if (client == c) {
valid = true;
break;
}
}
mutex_unlock(&drvdata->mlock);
}
if (!valid)
DI_ERR("invalid client[0x%p]\n", c);
return valid;
}
int di_drv_client_inc(struct di_client *c)
{
struct di_driver_data *drvdata = di_drvdata;
int client_cnt;
mutex_lock(&drvdata->mlock);
client_cnt = drvdata->client_cnt + 1;
if (client_cnt > DI_CLIENT_CNT_MAX) {
mutex_unlock(&drvdata->mlock);
DI_ERR(TAG"%s: %d > max_clients[%d]\n",
__func__, client_cnt, DI_CLIENT_CNT_MAX);
return -EINVAL;
}
drvdata->client_cnt = client_cnt;
list_add_tail(&c->node, &drvdata->clients);
di_check_enable_device_locked(drvdata);
mutex_unlock(&drvdata->mlock);
return 0;
}
int di_drv_client_dec(struct di_client *c)
{
struct di_driver_data *drvdata = di_drvdata;
int client_cnt;
mutex_lock(&drvdata->mlock);
list_del(&c->node);
if (drvdata->pre_client == c) {
drvdata->pre_client = NULL;
drvdata->need_apply_fixed_para = true;
}
client_cnt = drvdata->client_cnt;
if (client_cnt > 0) {
drvdata->client_cnt--;
di_check_enable_device_locked(drvdata);
} else {
mutex_unlock(&drvdata->mlock);
DI_INFO(TAG"%s:client_cnt=%d\n", __func__, client_cnt);
return -EINVAL;
}
mutex_unlock(&drvdata->mlock);
return 0;
}
static int di_drv_wait2start(
struct di_driver_data *drvdata, struct di_client *c)
{
const u64 wait2start = c->timeout.wait4start;
long ret = 0;
unsigned long flags;
u32 id;
u32 wait_con;
spin_lock_irqsave(&drvdata->queue_lock, flags);
if (drvdata->task_cnt >= DI_TASK_CNT_MAX) {
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
DI_ERR(TAG"too many tasks %d\n", drvdata->task_cnt);
return -EBUSY;
}
id = (drvdata->front + drvdata->task_cnt) % DI_TASK_CNT_MAX;
drvdata->queue[id] = c;
drvdata->task_cnt++;
if (drvdata->state == DI_DRV_STATE_IDLE) {
drvdata->state = DI_DRV_STATE_BUSY;
atomic_set(&c->wait_con, DI_PROC_STATE_2START);
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
return 0;
}
if (wait2start == 0) {
drvdata->queue[id] = NULL;
drvdata->task_cnt--;
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
DI_ERR(TAG"wait4start=%lluns too short to wait\n",
wait2start);
return -ETIMEDOUT;
}
atomic_set(&c->wait_con, DI_PROC_STATE_WAIT2START);
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
ret = wait_event_interruptible_hrtimeout(c->wait,
atomic_read(&c->wait_con) == DI_PROC_STATE_2START,
ns_to_ktime(wait2start));
if (atomic_read(&c->wait_con) != DI_PROC_STATE_2START) {
spin_lock_irqsave(&drvdata->queue_lock, flags);
wait_con = atomic_read(&c->wait_con); /* check-again */
if (wait_con != DI_PROC_STATE_2START) {
drvdata->queue[id] = NULL;
drvdata->task_cnt--;
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
DI_ERR(TAG"wait2start(%lluns) fail, con=%u, ret(%ld)\n",
wait2start, wait_con, ret);
return -ETIMEDOUT;
}
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
}
return 0;
}
static int di_drv_wait4finish(
struct di_driver_data *drvdata, struct di_client *c)
{
long ret = 0;
unsigned long flags;
int wait_con;
const u64 wait4finish = c->timeout.wait4finish;
ret = wait_event_interruptible_hrtimeout(c->wait,
atomic_read(&c->wait_con) != DI_PROC_STATE_WAIT4FINISH,
ns_to_ktime(wait4finish));
if (atomic_read(&c->wait_con) != DI_PROC_STATE_FINISH) {
spin_lock_irqsave(&drvdata->queue_lock, flags);
wait_con = atomic_read(&c->wait_con); /* check-again */
if (wait_con == DI_PROC_STATE_WAIT4FINISH) {
di_dev_reset();
di_dev_query_state_with_clear(DI_IRQ_STATE_PROC_FINISH);
drvdata->queue[drvdata->front] = NULL;
drvdata->front = (drvdata->front + 1) % DI_TASK_CNT_MAX;
drvdata->task_cnt--;
drvdata->state = DI_DRV_STATE_IDLE;
}
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
if (wait_con == DI_PROC_STATE_WAIT4FINISH) {
DI_ERR(TAG"wait4finish(%lluns) timeout, ret=%ld\n",
wait4finish, ret);
return ret ? ret : -ETIME;
} else if (wait_con != DI_PROC_STATE_FINISH) {
DI_ERR(TAG"wait4finish(%lluns) err, ret=%ld, con=%u\n",
wait4finish, ret, wait_con);
return ret ? ret : -wait_con;
}
}
DI_DEBUG("Processing frame %llu\n", c->proc_fb_seqno);
c->proc_fb_seqno++;
return 0;
}
static inline void di_drv_start(
struct di_driver_data *drvdata, struct di_client *c)
{
unsigned long flags;
spin_lock_irqsave(&drvdata->queue_lock, flags);
atomic_set(&c->wait_con, DI_PROC_STATE_WAIT4FINISH);
di_dev_start(1);
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
}
static void di_drv_survey_spot(
struct di_driver_data *drvdata, struct di_client *c)
{
struct di_client *pre_client = NULL;
mutex_lock(&drvdata->mlock);
if (((drvdata->pre_client == NULL)
&& (drvdata->need_apply_fixed_para == false))
|| (drvdata->pre_client == c))
goto out;
pre_client = drvdata->pre_client;
if (pre_client) {
if ((pre_client->proc_fb_seqno > 0)
&& (pre_client->para_checked == true))
di_dev_save_spot(pre_client);
}
di_dev_restore_spot(c);
c->apply_fixed_para = true;
out:
drvdata->pre_client = c;
drvdata->need_apply_fixed_para = false;
mutex_unlock(&drvdata->mlock);
}
/* caller must make sure c is valid */
int di_drv_process_fb(struct di_client *c)
{
struct di_driver_data *drvdata = di_drvdata;
int ret = 0;
ret = di_drv_wait2start(drvdata, c);
if (ret)
return ret;
di_drv_survey_spot(drvdata, c);
if (unlikely(c->apply_fixed_para)) {
c->apply_fixed_para = false;
di_dev_apply_fixed_para(c);
}
ret = di_dev_apply_para(c);
di_dev_dump_reg_value();
di_drv_start(drvdata, c);
ret |= di_drv_wait4finish(drvdata, c);
return ret;
}
static irqreturn_t di_irq_handler(int irq, void *dev_id)
{
struct di_driver_data *drvdata = dev_id;
unsigned long flags;
struct di_client *c;
int wait_con;
u32 hw_state;
if (irq != drvdata->irq_no)
return IRQ_NONE;
spin_lock_irqsave(&drvdata->queue_lock, flags);
hw_state = di_dev_query_state_with_clear(DI_IRQ_STATE_PROC_FINISH);
if (drvdata->task_cnt == 0)
goto irq_out;
c = drvdata->queue[drvdata->front];
wait_con = atomic_read(&c->wait_con);
if (wait_con == DI_PROC_STATE_WAIT4FINISH) {
if (hw_state & DI_IRQ_STATE_PROC_FINISH) {
di_dev_get_proc_result(c);
atomic_set(&c->wait_con, DI_PROC_STATE_FINISH);
wake_up_interruptible(&c->wait);
} else {
di_dev_reset();
atomic_set(&c->wait_con, DI_PROC_STATE_FINISH_ERR);
wake_up_interruptible(&c->wait);
}
drvdata->queue[drvdata->front] = NULL;
drvdata->task_cnt--;
drvdata->state = DI_DRV_STATE_IDLE;
if (drvdata->task_cnt == 0)
goto irq_out;
drvdata->front = (drvdata->front + 1) % DI_TASK_CNT_MAX;
c = drvdata->queue[drvdata->front];
wait_con = atomic_read(&c->wait_con);
}
if (wait_con == DI_PROC_STATE_WAIT2START) {
atomic_set(&c->wait_con, DI_PROC_STATE_2START);
drvdata->state = DI_DRV_STATE_BUSY;
wake_up_interruptible(&c->wait);
}
irq_out:
spin_unlock_irqrestore(&drvdata->queue_lock, flags);
return IRQ_HANDLED;
}
/* unload resources of di device */
static void di_unload_resource(struct di_driver_data *drvdata)
{
if (drvdata->reg_base)
iounmap(drvdata->reg_base);
if (drvdata->irq_no != 0)
DI_INFO(TAG"maybe should ummap irq[%d]...\n", drvdata->irq_no);
if (!IS_ERR_OR_NULL(drvdata->clk_source))
clk_put(drvdata->clk_source);
if (!IS_ERR_OR_NULL(drvdata->iclk))
clk_put(drvdata->iclk);
}
/* parse and load resources of di device */
static int di_parse_dt(struct platform_device *pdev,
struct di_driver_data *drvdata)
{
int ret = 0;
struct device_node *node = pdev->dev.of_node;
/* clk */
drvdata->iclk = of_clk_get(node, 0);
if (IS_ERR_OR_NULL(drvdata->iclk)) {
DI_ERR(TAG"get di clock failed!\n");
ret = PTR_ERR(drvdata->iclk);
goto err_out;
}
/*drvdata->clk_source = of_clk_get(node, 1);
if (IS_ERR_OR_NULL(drvdata->clk_source)) {
DI_ERR(TAG"get clk_source clock failed!\n");
ret = PTR_ERR(drvdata->clk_source);
goto err_out;
}*/
/* fixme: set iclk's parent as clk_source */
/* irq */
drvdata->irq_no = irq_of_parse_and_map(node, 0);
if (drvdata->irq_no == 0) {
DI_ERR(TAG"platform_get_irq failed!\n");
ret = -EINVAL;
goto err_out;
}
ret = devm_request_irq(&pdev->dev, drvdata->irq_no,
di_irq_handler, 0, dev_name(&pdev->dev), drvdata);
if (ret) {
DI_ERR(TAG"devm_request_irq failed\n");
goto err_out;
}
DI_DEBUG(TAG"di irq_no=%u\n", drvdata->irq_no);
/* reg */
drvdata->reg_base = of_iomap(node, 0);
if (!drvdata->reg_base) {
DI_ERR(TAG"of_iomap failed\n");
ret = -ENOMEM;
goto err_out;
}
DI_DEBUG(TAG"di reg_base=0x%p\n", drvdata->reg_base);
err_out:
return ret;
}
static int di_probe(struct platform_device *pdev)
{
int ret = 0;
struct device_node *node = pdev->dev.of_node;
struct di_driver_data *drvdata = NULL;
if (!of_device_is_available(node)) {
DI_ERR(TAG"DEINTERLACE device is not configed\n");
return -ENODEV;
}
drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL);
if (drvdata == NULL) {
DI_ERR(TAG"kzalloc for drvdata failed\n");
return -ENOMEM;
}
ret = di_parse_dt(pdev, drvdata);
if (ret)
goto probe_done;
clk_prepare_enable(drvdata->iclk);
di_utils_set_dma_dev(&pdev->dev);
ret = di_init_hw(drvdata);
if (ret)
goto probe_done;
mutex_init(&drvdata->mlock);
INIT_LIST_HEAD(&drvdata->clients);
spin_lock_init(&drvdata->queue_lock);
alloc_chrdev_region(&drvdata->devt, 0, 1, DI_MODULE_NAME);
drvdata->pcdev = cdev_alloc();
cdev_init(drvdata->pcdev, &di_fops);
drvdata->pcdev->owner = THIS_MODULE;
ret = cdev_add(drvdata->pcdev, drvdata->devt, 1);
if (ret) {
DI_ERR(TAG"cdev add major(%d).\n", MAJOR(drvdata->devt));
goto probe_done;
}
drvdata->pclass = class_create(THIS_MODULE, DI_MODULE_NAME);
if (IS_ERR(drvdata->pclass)) {
DI_ERR(TAG"create class error\n");
ret = PTR_ERR(drvdata->pclass);
goto probe_done;
}
drvdata->pdev = device_create_with_groups(
drvdata->pclass, NULL, drvdata->devt,
NULL, di_device_attr_groups,
DI_MODULE_NAME);
if (IS_ERR(drvdata->pdev)) {
DI_ERR(TAG"device_create error\n");
ret = PTR_ERR(drvdata->pdev);
goto probe_done;
}
di_drvdata = drvdata;
platform_set_drvdata(pdev, (void *)drvdata);
do {
struct di_version version;
di_drv_get_version(&version);
dev_info(&pdev->dev, "version[%d.%d.%d], ip=0x%x\n",
version.version_major,
version.version_minor,
version.version_patchlevel,
version.ip_version);
} while (0);
return 0;
probe_done:
if (ret) {
di_dev_exit();
di_unload_resource(drvdata);
kfree(drvdata);
dev_err(&pdev->dev, "probe failed, errno %d!\n", ret);
}
return ret;
}
static int di_remove(struct platform_device *pdev)
{
struct di_driver_data *drvdata;
dev_info(&pdev->dev, "%s\n", __func__);
drvdata = platform_get_drvdata(pdev);
if (drvdata != NULL) {
platform_set_drvdata(pdev, NULL);
di_drvdata = NULL;
if (drvdata->client_cnt > 0)
DI_ERR(TAG"still has client_cnt=%d\n",
drvdata->client_cnt);
device_destroy(drvdata->pclass, drvdata->devt);
class_destroy(drvdata->pclass);
cdev_del(drvdata->pcdev);
unregister_chrdev_region(drvdata->devt, 1);
di_dev_exit();
di_unload_resource(drvdata);
kfree(drvdata);
}
return 0;
}
static int di_suspend(struct device *dev)
{
struct di_driver_data *drvdata = di_drvdata;
if (drvdata->state == DI_DRV_STATE_BUSY)
DI_INFO(TAG"drv busy on suspend !\n");
mutex_lock(&drvdata->mlock);
drvdata->pm_state = DI_PM_STATE_SUSPEND;
if (drvdata->dev_enable == true) {
if (drvdata->pre_client)
di_dev_save_spot(drvdata->pre_client);
di_dev_enable_irq(DI_IRQ_FLAG_PROC_FINISH, 0);
if (di_clk_disable(drvdata))
drvdata->dev_enable = false;
}
mutex_unlock(&drvdata->mlock);
return 0;
}
static int di_resume(struct device *dev)
{
struct di_driver_data *drvdata = di_drvdata;
mutex_lock(&drvdata->mlock);
if (drvdata->client_cnt > 0) {
if (di_clk_enable(drvdata))
drvdata->dev_enable = true;
di_dev_enable_irq(DI_IRQ_FLAG_PROC_FINISH, 1);
if (drvdata->pre_client) {
di_dev_restore_spot(drvdata->pre_client);
drvdata->pre_client->apply_fixed_para = true;
}
}
drvdata->pm_state = DI_PM_STATE_RESUME;
mutex_unlock(&drvdata->mlock);
return 0;
}
static const struct dev_pm_ops di_pm_ops = {
.suspend = di_suspend,
.resume = di_resume,
};
static const struct of_device_id di_dt_match[] = {
{.compatible = "allwinner,sunxi-deinterlace"},
{},
};
static struct platform_driver di_driver = {
.probe = di_probe,
.remove = di_remove,
.driver = {
.name = DI_MODULE_NAME,
.owner = THIS_MODULE,
.pm = &di_pm_ops,
.of_match_table = di_dt_match,
},
};
module_platform_driver(di_driver);
int debug_mask = DEBUG_LEVEL_ERR;
module_param_named(debug_mask, debug_mask, int, 0644);
MODULE_DEVICE_TABLE(of, di_dt_match);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hezuyao@allwinnertech.com");
MODULE_AUTHOR("zhengwanyu@allwinnertech.com");
MODULE_DESCRIPTION("Sunxi De-Interlace");