blob: dbaedc21ae4fbf8ef8be598750a0a61246613e21 [file] [log] [blame]
/*
* drivers/amlogic/media/vin/tvin/csi/csi.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/major.h>
#include <linux/amlogic/media/mipi/am_mipi_csi2.h>
#include "../tvin_global.h"
#include "../vdin/vdin_regs.h"
#include "../vdin/vdin_drv.h"
#include "../vdin/vdin_ctl.h"
#include "../tvin_format_table.h"
#include "../tvin_frontend.h"
#include "csi.h"
#define DEV_NAME "amvdec_csi"
#define DRV_NAME "amvdec_csi"
#define CLS_NAME "amvdec_csi"
#define MOD_NAME "amvdec_csi"
#define CSI_MAX_DEVS 1
#define WDG_STEP_JIFFIES 10
static dev_t amcsi_devno;
static struct class *amcsi_clsp;
static void init_csi_dec_parameter(struct amcsi_dev_s *devp)
{
enum tvin_sig_fmt_e fmt;
const struct tvin_format_s *fmt_info_p;
pr_info("init_csi_dec_parameter.\n");
fmt = devp->para.fmt;
fmt_info_p =
(struct tvin_format_s *)tvin_get_fmt_info(fmt);
devp->para.v_active = 1080;
devp->para.h_active = 1920;
devp->para.hsync_phase = 0;
devp->para.vsync_phase = 0;
devp->para.hs_bp = 0;
devp->para.vs_bp = 0;
devp->para.csi_hw_info.lanes = 2;
}
static void reset_btcsi_module(void)
{
DPRINT("%s, %d\n", __func__, __LINE__);
}
static void reinit_csi_dec(struct amcsi_dev_s *devp)
{
DPRINT("%s, %d\n", __func__, __LINE__);
}
static void start_amvdec_csi(struct amcsi_dev_s *devp)
{
enum tvin_port_e port = devp->para.port;
pr_info("start_amvdec_csi.\n");
if (devp->dec_status & TVIN_AMCSI_RUNNING) {
pr_info("%s csi have started alreadly.\n",
__func__);
return;
}
devp->dec_status = TVIN_AMCSI_RUNNING;
pr_info("start_amvdec_csi port = %x\n", port);
if (port == TVIN_PORT_MIPI) {
init_csi_dec_parameter(devp);
reinit_csi_dec(devp);
} else {
devp->para.fmt = TVIN_SIG_FMT_NULL;
devp->para.port = TVIN_PORT_NULL;
DPRINT("%s: input is not selected, please try again\n",
__func__);
return;
}
devp->dec_status = TVIN_AMCSI_RUNNING;
}
static void stop_amvdec_csi(struct amcsi_dev_s *devp)
{
if (devp->dec_status & TVIN_AMCSI_RUNNING) {
reset_btcsi_module();
devp->dec_status = TVIN_AMCSI_STOP;
} else
DPRINT("%s device is not started yet\n", __func__);
}
static bool amcsi_check_skip_frame(struct tvin_frontend_s *fe)
{
struct amcsi_dev_s *devp =
container_of(fe, struct amcsi_dev_s, frontend);
if (devp->csi_parm.skip_frames > 0) {
devp->csi_parm.skip_frames--;
return true;
} else
return false;
}
int amcsi_support(struct tvin_frontend_s *fe, enum tvin_port_e port)
{
if (port != TVIN_PORT_MIPI) {
DPRINT("this is not MIPI port\n");
return -1;
} else {
return 0;
}
}
static int amcsi_open(struct inode *node, struct file *file)
{
struct amcsi_dev_s *csi_devp;
csi_devp = container_of(node->i_cdev, struct amcsi_dev_s, cdev);
file->private_data = csi_devp;
return 0;
}
static int amcsi_release(struct inode *node, struct file *file)
{
file->private_data = NULL;
return 0;
}
static const struct file_operations amcsi_fops = {
.owner = THIS_MODULE,
.open = amcsi_open,
.release = amcsi_release,
};
void amcsi_start(struct tvin_frontend_s *fe, enum tvin_sig_fmt_e fmt)
{
struct amcsi_dev_s *csi_devp;
csi_devp = container_of(fe, struct amcsi_dev_s, frontend);
start_amvdec_csi(csi_devp);
}
static void amcsi_stop(struct tvin_frontend_s *fe, enum tvin_port_e port)
{
struct amcsi_dev_s *devp =
container_of(fe, struct amcsi_dev_s, frontend);
if (port != TVIN_PORT_MIPI) {
DPRINT("%s:invaild port %d.\n", __func__, port);
return;
}
stop_amvdec_csi(devp);
}
void amcsi_get_sig_property(struct tvin_frontend_s *fe,
struct tvin_sig_property_s *prop)
{
struct amcsi_dev_s *devp =
container_of(fe, struct amcsi_dev_s, frontend);
prop->color_format = devp->para.cfmt;
prop->dest_cfmt = devp->para.dfmt;
pr_info("devp->para.cfmt=%d, devp->para.dfmt=%d\n",
devp->para.cfmt, devp->para.dfmt);
prop->decimation_ratio = 0;
}
int amcsi_isr(struct tvin_frontend_s *fe, unsigned int hcnt)
{
struct amcsi_dev_s *devp =
container_of(fe, struct amcsi_dev_s, frontend);
unsigned int data1 = 0;
struct am_csi2_frame_s frame;
frame.w = READ_CSI_ADPT_REG_BIT(CSI2_PIC_SIZE_STAT, 0, 16);
frame.h = READ_CSI_ADPT_REG_BIT(CSI2_PIC_SIZE_STAT, 16, 16);
frame.err = READ_CSI_ADPT_REG(CSI2_ERR_STAT0);
data1 = READ_CSI_ADPT_REG(CSI2_GEN_STAT0);
if (frame.err) {
DPRINT("%s,error---pixel cnt:%d, line cnt:%d\n",
__func__, frame.w, frame.h);
DPRINT("error state:0x%x.,status:0x%x\n",
frame.err, data1);
devp->overflow_cnt++;
WRITE_CSI_ADPT_REG(CSI2_ERR_STAT0, 0);
}
if (devp->overflow_cnt > 4) {
DPRINT("should reset mipi\n");
devp->overflow_cnt = 0;
return 0;
}
devp->reset = 0;
return 0;
}
static ssize_t csi_attr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t len = 0;
struct amcsi_dev_s *csi_devp;
int i;
csi_devp = dev_get_drvdata(dev);
if (csi_devp->dec_status != TVIN_AMCSI_RUNNING) {
len += sprintf(buf+len, "csi does not start\n");
return len;
}
len += sprintf(buf+len, "csi parameters below\n");
len +=
sprintf(buf+len, "\tlanes=%d, channel=%d\n"
"\tclk_channel=%d\n"
"\tmode=%d, clock_lane_mode=%d, active_pixel=%d\n"
"\tactive_line=%d, frame_size=%d, ui_val=%dns\n"
"\ths_freq=%dhz, urgent=%d\n",
csi_devp->csi_parm.lanes,
csi_devp->csi_parm.channel,
csi_devp->csi_parm.clk_channel,
csi_devp->csi_parm.mode,
csi_devp->csi_parm.clock_lane_mode,
csi_devp->csi_parm.active_pixel,
csi_devp->csi_parm.active_line,
csi_devp->csi_parm.frame_size,
csi_devp->csi_parm.ui_val,
csi_devp->csi_parm.hs_freq,
csi_devp->csi_parm.urgent);
len += sprintf(buf+len, "csi adapter register below\n");
for (i = CSI_ADPT_START_REG; i <= CSI_ADPT_END_REG; i++) {
len += sprintf(buf+len, "\t[0x%04x]=0x%08x\n",
i - CSI_ADPT_START_REG, READ_CSI_ADPT_REG(i));
}
len += sprintf(buf+len, "csi phy register below\n");
for (i = CSI_PHY_START_REG; i <= CSI_PHY_END_REG; i++) {
len += sprintf(buf+len, "\t[0x%04x]=0x%08x\n",
i, READ_CSI_PHY_REG(i));
}
len += sprintf(buf+len, "csi host register below\n");
for (i = CSI_HST_START_REG; i <= CSI_HST_END_REG; i++) {
len += sprintf(buf+len, "\t[0x%04x]=0x%08x\n",
i << 2, READ_CSI_HST_REG(i));
}
return len;
}
static ssize_t csi_attr_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct amcsi_dev_s *csi_devp;
unsigned int n = 0;
char *buf_orig, *ps, *token;
char *parm[6] = {NULL};
if (!buf)
return len;
buf_orig = kstrdup(buf, GFP_KERNEL);
csi_devp = dev_get_drvdata(dev);
ps = buf_orig;
while (1) {
if (n >= ARRAY_SIZE(parm)) {
pr_info("parm array overflow, n=%d\n", n);
kfree(buf_orig);
return len;
}
token = strsep(&ps, "\n");
if (token == NULL)
break;
if (*token == '\0')
continue;
parm[n++] = token;
}
if (strcmp(parm[0], "reset") == 0) {
pr_info("reset\n");
am_mipi_csi2_init(&csi_devp->csi_parm);
} else if (strcmp(parm[0], "init") == 0) {
pr_info("init mipi measure clock\n");
init_am_mipi_csi2_clock();
} else if (strcmp(parm[0], "min") == 0) {
csi_devp->min_frmrate =
kstrtol(parm[1], 16, NULL);
if ((csi_devp->min_frmrate * WDG_STEP_JIFFIES) < HZ)
csi_devp->min_frmrate = HZ/WDG_STEP_JIFFIES;
pr_info("min_frmrate=%d\n", csi_devp->min_frmrate);
}
kfree(buf_orig);
return len;
}
static DEVICE_ATTR(hw_info, 0664, csi_attr_show, csi_attr_store);
static int amcsi_feopen(struct tvin_frontend_s *fe, enum tvin_port_e port)
{
struct amcsi_dev_s *csi_devp =
container_of(fe, struct amcsi_dev_s, frontend);
struct vdin_parm_s *parm = fe->private_data;
if (port != TVIN_PORT_MIPI) {
DPRINT("[mipi..]%s:invaild port %d.\n", __func__, port);
return -1;
}
if (!memcpy(&csi_devp->para, parm,
sizeof(struct vdin_parm_s))) {
DPRINT("[mipi..]%s memcpy error.\n", __func__);
return -1;
}
init_am_mipi_csi2_clock();
csi_devp->para.port = port;
memcpy(&csi_devp->csi_parm,
&parm->csi_hw_info, sizeof(struct csi_parm_s));
csi_devp->csi_parm.skip_frames = parm->skip_count;
csi_devp->reset = 0;
csi_devp->reset_count = 0;
cal_csi_para(&csi_devp->csi_parm);
am_mipi_csi2_init(&csi_devp->csi_parm);
return 0;
}
static void amcsi_feclose(struct tvin_frontend_s *fe)
{
struct amcsi_dev_s *devp =
container_of(fe, struct amcsi_dev_s, frontend);
enum tvin_port_e port = devp->para.port;
if (port != TVIN_PORT_MIPI) {
DPRINT("[mipi..]%s:invaild port %d.\n", __func__, port);
return;
}
devp->reset = 0;
devp->reset_count = 0;
am_mipi_csi2_uninit();
memset(&devp->para, 0, sizeof(struct vdin_parm_s));
}
static struct tvin_state_machine_ops_s amcsi_machine_ops = {
.nosig = NULL,
.fmt_changed = NULL,
.get_fmt = NULL,
.fmt_config = NULL,
.adc_cal = NULL,
.pll_lock = NULL,
.get_sig_property = amcsi_get_sig_property,
.vga_set_param = NULL,
.vga_get_param = NULL,
.check_frame_skip = amcsi_check_skip_frame,
};
static struct tvin_decoder_ops_s amcsi_decoder_ops_s = {
.support = amcsi_support,
.open = amcsi_feopen,
.start = amcsi_start,
.stop = amcsi_stop,
.close = amcsi_feclose,
.decode_isr = amcsi_isr,
};
static int csi_add_cdev(struct cdev *cdevp,
const struct file_operations *fops, int minor)
{
int ret;
dev_t devno = MKDEV(MAJOR(amcsi_devno), minor);
cdev_init(cdevp, fops);
cdevp->owner = THIS_MODULE;
ret = cdev_add(cdevp, devno, 1);
return ret;
}
static void csi_delete_device(int minor)
{
dev_t devno = MKDEV(MAJOR(amcsi_devno), minor);
device_destroy(amcsi_clsp, devno);
}
static int amvdec_csi_probe(struct platform_device *pdev)
{
int ret = 0;
int id = 0;
struct amcsi_dev_s *devp = NULL;
devp = kmalloc(sizeof(struct amcsi_dev_s), GFP_KERNEL);
if (!devp) {
ret = -1;
goto fail_kmalloc_dev;
}
memset(devp, 0, sizeof(struct amcsi_dev_s));
ret = csi_add_cdev(&devp->cdev, &amcsi_fops, 0);
if (ret != 0) {
pr_err("%s: failed to add cdev\n", __func__);
goto fail_add_cdev;
}
ret = of_property_read_u32(pdev->dev.of_node, "csi_id", &id);
if (ret != 0) {
pr_err("%s: don't find csi_id.\n", __func__);
goto fail_add_cdev;
}
pdev->id = id;
sprintf(devp->frontend.name, "%s", DEV_NAME);
tvin_frontend_init(&devp->frontend, &amcsi_decoder_ops_s,
&amcsi_machine_ops, pdev->id);
tvin_reg_frontend(&devp->frontend);
devp->pdev = pdev;
platform_set_drvdata(pdev, devp);
am_mipi_csi2_para_init(pdev);
pr_info("amvdec_csi probe ok.\n");
return ret;
fail_add_cdev:
kfree(devp);
fail_kmalloc_dev:
return ret;
}
static int amvdec_csi_remove(struct platform_device *pdev)
{
struct amcsi_dev_s *devp;
devp = (struct amcsi_dev_s *)platform_get_drvdata(pdev);
tvin_unreg_frontend(&devp->frontend);
device_remove_file(devp->dev, &dev_attr_hw_info);
deinit_am_mipi_csi2_clock();
csi_delete_device(pdev->id);
cdev_del(&devp->cdev);
dev_set_drvdata(devp->dev, NULL);
platform_set_drvdata(pdev, NULL);
kfree(devp);
return 0;
}
static const struct of_device_id csi_dt_match[] = {
{
.compatible = "amlogic, amvdec_csi",
},
{},
};
static struct platform_driver amvdec_csi_driver = {
.probe = amvdec_csi_probe,
.remove = amvdec_csi_remove,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = csi_dt_match,
}
};
static int __init amvdec_csi_init_module(void)
{
int ret = 0;
pr_info("amvdec_csi module: init.\n");
ret = alloc_chrdev_region(&amcsi_devno, 0,
CSI_MAX_DEVS, DEV_NAME);
if (ret != 0) {
pr_err("%s:failed to alloc major number\n",
__func__);
goto fail_alloc_cdev_region;
}
pr_info("%s:major %d\n", __func__, MAJOR(amcsi_devno));
amcsi_clsp = class_create(THIS_MODULE, CLS_NAME);
if (IS_ERR(amcsi_clsp)) {
ret = PTR_ERR(amcsi_clsp);
pr_err("%s:failed to create class\n", __func__);
goto fail_class_create;
}
ret = platform_driver_register(&amvdec_csi_driver);
if (ret) {
pr_err("failed to register amvdec_csi driver\n");
goto fail_pdrv_register;
}
pr_info("amvdec_csi module: init. ok\n");
return 0;
fail_pdrv_register:
class_destroy(amcsi_clsp);
fail_class_create:
unregister_chrdev_region(amcsi_devno, CSI_MAX_DEVS);
fail_alloc_cdev_region:
pr_err("amvdec_csi module: init failed, ret=%d\n", ret);
return ret;
}
static void __exit amvdec_csi_exit_module(void)
{
pr_info("amvdec_csi module remove.\n");
DPRINT("%s, %d\n", __func__, __LINE__);
class_destroy(amcsi_clsp);
unregister_chrdev_region(amcsi_devno, CSI_MAX_DEVS);
platform_driver_unregister(&amvdec_csi_driver);
}
module_init(amvdec_csi_init_module);
module_exit(amvdec_csi_exit_module);
MODULE_DESCRIPTION("AMLOGIC CSI input driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.0");