Bringup ti SN65DSI83-Q1/SN65DSI84-Q1

版权声明:本文为博主原创文章,欢迎转载,转载请注明转载地址 https://blog.csdn.net/u012839187/article/details/84679976

SN65DSI83是一个mipi转lvds的单路对单路转换器。-Q1个人认为是车规级产品

#SN65DSI84是mipi转lvds单路对双路转换器

#需要注意的是mipi clock以及lvds clock的计算方法. [github的imx6qp.sh上面有详解https://github.com/mazelinux/PATEO/blob/master/imx/imx6qp_readme.sh]

#一路mipi转两路lvds,需要注意h方向的分辨率:mipi是lvds的两倍.显示部分和隐藏部分均是.

支持I2c 挂载。i2c的从地址通过

将bit1的addr上拉或者下拉来判断.我们的电路图是下拉。所以address是0x58。

bringup的逻辑简单来说分几部分:dts声明设备的存在,对应驱动来驱动设备,硬件上接好设备。

1.dts

 &i2c_5 { /* BLSP2 QUP1 */
+         tixx@2c { /*ti csr ic:SN65DSI83-Q1 dsi2lvds*/
+                 compatible = "ti,tixx";
+                 reg = <0x2c>;
+         };
 };

2.驱动

/*
 * Copyright 2005-2014 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * 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 tixx.c
 *
 * @brief tixx GMSL1 DSI Serializer driver
 *
 * @ingroup LCD
 */
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/notifier.h>

/***********************************************************************
 * WORKQUEUE .
 ***********************************************************************/
static struct workqueue_struct * queue = NULL;
static struct delayed_work work;
static int work_handler(struct work_struct *data);
static int notifier_handler(void);

struct sensor {
	struct i2c_client *i2c_client;
} tixx_data;
/***********************************************************************
 * NOFITIER .
 ***********************************************************************/
static BLOCKING_NOTIFIER_HEAD(dsi84_reinit_chain);
static struct notifier_block dsi84_reinit_notifier =
{
    .notifier_call = notifier_handler,
};

int dsi84_reinit_notifier_call_chain(unsigned long val, void *v)
{
    return blocking_notifier_call_chain(&dsi84_reinit_chain, val, v);
}
EXPORT_SYMBOL(dsi84_reinit_notifier_call_chain);
/***********************************************************************
 * I2C transfert.
 ***********************************************************************/

/*! Read one register from a tixx i2c slave device.
 *
 *	@param *reg		register in the device we wish to access.
 *
 *	@return			   0 if success, an error code otherwise.
 */
static inline int tixx_read_reg(u8 reg)
{
	int val;
	val = i2c_smbus_read_byte_data(tixx_data.i2c_client, reg);
	return val;
}

/*! Write one register of a tixx i2c slave device.
 *
 *	@param *reg		register in the device we wish to access.
 *
 *	@return			   0 if success, an error code otherwise.
 */
static int tixx_write_reg(u8 reg, u8 val)
{
	s32 ret;
	ret = i2c_smbus_write_byte_data(tixx_data.i2c_client, reg, val);
	if (ret < 0) {
		pr_err("%s:write reg error:reg=%2x,val=%2x\n", __func__,
			reg, val);
	}
    msleep(10);
	return ret;
}

static int sn65dsi84_init(void)
{
	int ret = 0;
	pr_debug("%s\n",__func__);
	ret |= tixx_write_reg(0x09, 0x01);
	ret |= tixx_write_reg(0x0A, 0x03); //lvds_clk_range :0101 – 60.7Mhz---> 62.5 MHz ≤ LVDS_CLK < 87.5 MHz
	ret |= tixx_write_reg(0x0B, 0x28); //dsi_clk_divider:
	ret |= tixx_write_reg(0x0D, 0x00);
	ret |= tixx_write_reg(0x10, 0x26);
	ret |= tixx_write_reg(0x11, 0x00);
	ret |= tixx_write_reg(0x12, 0x37); //dsi_clk_range	:
	ret |= tixx_write_reg(0x13, 0x00);
	ret |= tixx_write_reg(0x18, 0x6c);
	ret |= tixx_write_reg(0x19, 0x00);
	ret |= tixx_write_reg(0x1A, 0x03);
	ret |= tixx_write_reg(0x1B, 0x00);
	ret |= tixx_write_reg(0x20, 0x80);//
	ret |= tixx_write_reg(0x21, 0x07);//1920
	ret |= tixx_write_reg(0x22, 0x00);
	ret |= tixx_write_reg(0x23, 0x00);
	ret |= tixx_write_reg(0x24, 0x00);
	ret |= tixx_write_reg(0x25, 0x00);
	ret |= tixx_write_reg(0x26, 0x00);
	ret |= tixx_write_reg(0x27, 0x00);
	ret |= tixx_write_reg(0x28, 0x21);
	ret |= tixx_write_reg(0x29, 0x00);
	ret |= tixx_write_reg(0x2A, 0x00);
	ret |= tixx_write_reg(0x2B, 0x00);
	ret |= tixx_write_reg(0x2C, 0x14);//
	ret |= tixx_write_reg(0x2D, 0x00);// hpw
	ret |= tixx_write_reg(0x2E, 0x00);
	ret |= tixx_write_reg(0x2F, 0x00);
	ret |= tixx_write_reg(0x30, 0x0a);//
	ret |= tixx_write_reg(0x31, 0x00);// vpw
	ret |= tixx_write_reg(0x32, 0x00);
	ret |= tixx_write_reg(0x33, 0x00);
	ret |= tixx_write_reg(0x34, 0x18);// hbp
	ret |= tixx_write_reg(0x35, 0x00);
	ret |= tixx_write_reg(0x36, 0x00);// vbp
	ret |= tixx_write_reg(0x37, 0x00);
	ret |= tixx_write_reg(0x38, 0x00);// hfp
	ret |= tixx_write_reg(0x39, 0x00);
	ret |= tixx_write_reg(0x3A, 0x00);// vfp
	ret |= tixx_write_reg(0x3B, 0x00);
	ret |= tixx_write_reg(0x3C, 0x00);// test
	ret |= tixx_write_reg(0x3D, 0x00);
	ret |= tixx_write_reg(0x3E, 0x00);
//	ret |= tixx_write_reg(0x09, 0x01);
	ret |= tixx_write_reg(0x0D, 0x01);
	ret |= tixx_write_reg(0x09, 0x00);
	return ret;
}

static int sn65dsi84_pattern_init(void)
{
//test
	int ret = 0;
	pr_debug("%s\n",__func__);
	ret |= tixx_write_reg(0x09 ,0x01);
	ret |= tixx_write_reg(0x0A ,0x03);
	ret |= tixx_write_reg(0x0B ,0x28);
	ret |= tixx_write_reg(0x0D ,0x00);
	ret |= tixx_write_reg(0x10 ,0x26);
	ret |= tixx_write_reg(0x11 ,0x00);
	ret |= tixx_write_reg(0x12 ,0x34);
	ret |= tixx_write_reg(0x13 ,0x00);
	ret |= tixx_write_reg(0x18 ,0x6c);
	ret |= tixx_write_reg(0x19 ,0x00);
	ret |= tixx_write_reg(0x1A ,0x03);
	ret |= tixx_write_reg(0x1B ,0x00);
	ret |= tixx_write_reg(0x20 ,0xc0);
	ret |= tixx_write_reg(0x21 ,0x03);
	ret |= tixx_write_reg(0x22 ,0x00);
	ret |= tixx_write_reg(0x23 ,0x00);
	ret |= tixx_write_reg(0x24 ,0xd0);
	ret |= tixx_write_reg(0x25 ,0x02);
	ret |= tixx_write_reg(0x26 ,0x00);
	ret |= tixx_write_reg(0x27 ,0x00);
	ret |= tixx_write_reg(0x28 ,0x20);
	ret |= tixx_write_reg(0x29 ,0x00);
	ret |= tixx_write_reg(0x2A ,0x00);
	ret |= tixx_write_reg(0x2B ,0x00);
	ret |= tixx_write_reg(0x2C ,0x14);
	ret |= tixx_write_reg(0x2D ,0x00);
	ret |= tixx_write_reg(0x2E ,0x00);
	ret |= tixx_write_reg(0x2F ,0x00);
	ret |= tixx_write_reg(0x30 ,0x02);
	ret |= tixx_write_reg(0x31 ,0x00);
	ret |= tixx_write_reg(0x32 ,0x00);
	ret |= tixx_write_reg(0x33 ,0x00);
	ret |= tixx_write_reg(0x34 ,0x18);
	ret |= tixx_write_reg(0x35 ,0x00);
	ret |= tixx_write_reg(0x36 ,0x04);
	ret |= tixx_write_reg(0x37 ,0x00);
	ret |= tixx_write_reg(0x38 ,0x14);
	ret |= tixx_write_reg(0x39 ,0x00);
	ret |= tixx_write_reg(0x3A ,0x02);
	ret |= tixx_write_reg(0x3B ,0x00);
	ret |= tixx_write_reg(0x3C ,0x10);
	ret |= tixx_write_reg(0x3D ,0x00);
	ret |= tixx_write_reg(0x3E ,0x00);
//	ret |= tixx_write_reg(0x09, 0x01);
	ret |= tixx_write_reg(0x0D, 0x01);
	ret |= tixx_write_reg(0x09, 0x00);
	return ret;
}

static int init_tixx_reg(void)
{
    {
#if 1
    printk("sn65dsi84_init\n");
	return sn65dsi84_init(); //1920*720
#else
	return sn65dsi84_pattern_init();
#endif
    }
}

bool check_chip_existence(void)
{
	if((tixx_read_reg(0x0) == 0x35)&&
		(tixx_read_reg(0x1) == 0x38)&&
		(tixx_read_reg(0x2) == 0x49))
		return true;
	return false;
}

int tixx_enable(int enable)
{
	int ret = 0;
	if (enable)
	{
		pr_debug("%s enter enable %d \n", __func__,enable);
		ret |= tixx_write_reg(0x09, 0x01);
		ret |= tixx_write_reg(0x0D, 0x01);
		msleep(10);
		ret |= tixx_write_reg(0xE5, 0xFF);
		ret |= tixx_write_reg(0xE0, 0x01);
		ret |= tixx_write_reg(0xE1, 0xFF);
	}
	else
	{
		pr_debug("%s enter disable %d \n", __func__,enable);
		ret |= tixx_write_reg(0x0D, 0x00);
	}
	return ret;
}
EXPORT_SYMBOL(tixx_enable);

static ssize_t tixx_pattern_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
    int len;
    int pattern;
    len = sscanf(buf, "%d", (unsigned int *)&pattern);
    pr_debug("read tixx pattern : %d\n",pattern);
    tixx_enable(0);

    if (pattern == 0)
	sn65dsi84_pattern_init();
    else 
    sn65dsi84_init();

    tixx_enable(1);
    return count;
}

//static ssize_t tixx_pattern_show(struct device *dev,
//				struct device_attribute *attr, const char *buf, size_t count)
//{
//    return 0;
//}

static ssize_t tixx_reg_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int len;
	u16 i;
	len = sprintf(buf, "result\n");
	for(i = 0; i <= 0xff; i++){
		msleep(10);
		pr_info("runtime_tixx: 0x%2X = 0x%2X\n", i, tixx_read_reg(i));
	}

	return len;
}

static ssize_t tixx_reg_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int len;
	int addr, val;
	len = sscanf(buf, "0x%x 0x%x", (unsigned int *)&addr ,(unsigned int *)&val);
	pr_info("write tixx addr: 0x%2X\tval:0x%2X\n", addr, val);
	tixx_write_reg(addr, val);
	return count;
}

static ssize_t tixx_reg_single_store(struct device *dev,
				struct device_attribute *attr, const char *buf, size_t count)
{
	int len;
	int addr;
	len = sscanf(buf, "0x%x", (unsigned int *)&addr);
	pr_info("read tixx addr: 0x%2X\tval:0x%2X\n", addr, tixx_read_reg(addr));
	return count;
}

static DEVICE_ATTR(pattern, (0664), NULL, tixx_pattern_store);
static DEVICE_ATTR(reg, (0644), tixx_reg_show, tixx_reg_store);
static DEVICE_ATTR(reg_single, (0644), NULL, tixx_reg_single_store);

static struct attribute *tixx_attributes[] = {
    &dev_attr_pattern.attr,
	&dev_attr_reg.attr,
	&dev_attr_reg_single.attr,
	NULL
};

static struct attribute_group tixx_attr_group = {
	.attrs = tixx_attributes,
};

static int tixx_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	int retval = 0;
	static struct kobject *tixx_kobj;
	tixx_data.i2c_client = client;
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		dev_err(&client->dev, "I2C not supported\n");
		return -ENODEV;
	}
	if(check_chip_existence() == false){
		pr_err("%s:no chip is found\n", __func__);
		return -EIO;
	}else{
		pr_info("%s:chip is found\n", __func__);
	}
	retval = init_tixx_reg();
	if(retval != 0)
		return -EIO;

	//tixx_kobj = kobject_create_and_add("tixx", kernel_kobj);
	//if(!tixx_kobj)
	//	return -ENOMEM;
	//retval = sysfs_create_group(tixx_kobj, &tixx_attr_group);

	retval = sysfs_create_group(&client->dev.kobj, &tixx_attr_group);
	if (retval) {
		kobject_put(tixx_kobj);
	}

    queue = create_workqueue("delay_resume_tixx");
    INIT_DELAYED_WORK(&work,work_handler);

    blocking_notifier_chain_register(&dsi84_reinit_chain, &dsi84_reinit_notifier);
    printk("tixx_probe success\n");
	return 0;
}

static int tixx_suspend(struct device *dev)
{
    pr_info("tixx going suspend \n");
    return 0;
}

static int tixx_resume(struct device *dev)
{
    queue_delayed_work(queue,&work,msecs_to_jiffies(500));
    return 0;
}

static void reset_dsi84(void)
{
    int i = 0;
    while(check_chip_existence() == false && i < 8)
    {
        msleep(1000);
        i++;
    }
    tixx_enable(0);
    sn65dsi84_init();
    tixx_enable(1);
}

static int work_handler(struct work_struct *data)
{
    pr_info("tixx going resume \n");
    reset_dsi84();
    return 0;
}

static int notifier_handler(void)
{
    pr_info("tixx going notifier \n");
    reset_dsi84();
    return 0;
}

static int tixx_remove(struct i2c_client *client)
{
    blocking_notifier_chain_unregister(&dsi84_reinit_chain, &dsi84_reinit_notifier);
    destroy_workqueue(queue);
	return 0;
}

static const struct i2c_device_id tixx_id[] = {
	{"tixx", 0},
	{},
};

MODULE_DEVICE_TABLE(i2c, tixx_id);

static const struct dev_pm_ops tixx_pm_ops = {
   .suspend = tixx_suspend,
    .resume = tixx_resume,
};

static struct i2c_driver tixx_i2c_driver = {
	.driver = {
		   .owner = THIS_MODULE,
		   .name = "tixx",
           .pm = &tixx_pm_ops,
		   },
	.probe = tixx_probe,
	.remove = tixx_remove,
	.id_table = tixx_id,
};

/*!
 * tixx init function.
 * Called on insmod.
 *
 * @return	  Error code indicating success or failure.
 */
static __init int tixx_init(void)
{
	u8 err = 0;

	/* Tells the i2c driver what functions to call for this driver. */
	err = i2c_add_driver(&tixx_i2c_driver);
	if (err != 0)
		pr_err("%s:driver registration failed, error=%d\n",
			__func__, err);

	return err;
}

/*!
 * tixx cleanup function.
 * Called on rmmod.
 *
 * @return	 Error code indicating success or failure.
 */
static void __exit tixx_clean(void)
{
	i2c_del_driver(&tixx_i2c_driver);
}

module_init(tixx_init);
module_exit(tixx_clean);

MODULE_AUTHOR("PATEO maze");
MODULE_DESCRIPTION("tixx GMSL1 DSI Serializer driver");
MODULE_LICENSE("GPL");
/*
 * Copyright 20018-2019 PATEO, Inc. All Rights Reserved.
 */

/*
 * 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
 */
#include <linux/types.h>
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/interrupt.h>

extern int dsi84_reinit_notifier_call_chain(unsigned long val, void *v);

static irqreturn_t wakeup_handler(int irq, void *dev_id)
{
    dsi84_reinit_notifier_call_chain(1, NULL);
	return IRQ_HANDLED;
}

/*!
 * mcu_state probe function.
 * Called on probe.
 * Request irq.
 *
 * @return	  Error code indicating success or failure.
 */
static int  mcu_state_probe(struct platform_device *pdev)
{
	int ret = 0;
    int pateo_gpio48 = 0; 
    struct device *dev = &pdev->dev;

	pateo_gpio48 = of_get_named_gpio(pdev->dev.of_node, "qcom,pateo_gpio48", 0);
	if (gpio_is_valid(pateo_gpio48)) {
	    ret = gpio_request( pateo_gpio48, "pateo_gpio48");
	    if(0 != ret) {
		    pr_err("gpio request %d failed.\n",pateo_gpio48);
            return ret;
	    }
	    ret = gpio_direction_input(pateo_gpio48);
	    if (ret) {
	        pr_err("Unable to set direction for irq gpio [%d]\n",pateo_gpio48);
        }
    }else{
		pr_err("Invalid pateo_gpio [%d]!\n",pateo_gpio48);
		ret = -EINVAL;
    }

    ret = request_threaded_irq(gpio_to_irq(pateo_gpio48), NULL, wakeup_handler, 
                IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev_name(dev), (void*)0);
    if (ret) {
        pr_err("Request threaded irq for [%d] failed!\n",pateo_gpio48);
    }
    printk("mcu_state_probe success\n");
    return ret;    
}

static int  mcu_state_remove(struct platform_device *pdev)
{
	return 0;
}

static int  mcu_state_suspend(struct platform_device *pdev,pm_message_t state)
{
	return 0;
}

static int  mcu_state_resume(struct platform_device *pdev)
{
	return 0;
}

static struct of_device_id  mcu_state_dt_match[] = {
	{ .compatible = "pateo_mcu_state",},
	{ },
};
MODULE_DEVICE_TABLE(of,  mcu_state_dt_match);

/*!
 * mcu_state driver struct.
 * Called on insmod.
 */
static struct platform_driver mcu_state_driver = {
	.driver = {
		.name = "mcu_state",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr( mcu_state_dt_match),
	},
	.probe =  mcu_state_probe,
	.remove =  mcu_state_remove,
	.suspend =  mcu_state_suspend,
	.resume =  mcu_state_resume,
};

/*!
 * mcu_state init function.
 * Called on insmod.
 *
 * @return	  Error code indicating success or failure.
 */
static __init int mcu_state_init(void)
{
	u8 err = 0;

	err =platform_driver_register(&mcu_state_driver);
	if (err != 0)
		pr_err("%s:driver registration failed, error=%d\n",
			__func__, err);

	return err;
}

/*!
 * mcu_state exit function.
 * Called on rmmod.
 *
 * @return	 Error code indicating success or failure.
 */
static void __exit mcu_state_exit(void)
{
	platform_driver_unregister(&mcu_state_driver);
}

module_init(mcu_state_init);
module_exit(mcu_state_exit);

MODULE_AUTHOR("PATEO maze");
MODULE_DESCRIPTION("PATEO mcu_state driver");
MODULE_LICENSE("GPL");

上面这段代码是因为瞬时电压在30v或者6v会导致mcu睡眠.睡眠会导致dsi84芯片寄存器错误.所以我们需要通过48引脚的触发事件来截取到这种操作并且重写dsi84寄存器.

设备的工作原理就是probe的时候往ti的ic里面写寄存器参数。代码里面比较尴尬的是不论dsi84或者dsi83,均是相同的deviceid,没办法通过读取寄存器确认目前是83的芯片还是84的芯片.[第一次更新:新加的代码是我在sys/kernel/tixx/加了一个pattern节点.这样就可以动态的去切换pattern和normal模式.以便在做测试的时候及时知道是dsi的转换芯片挂了还是平台端的mipi信号出了问题.]

确定寄存器的值是通过ti的tuner 工具。

可以去ti官网直接搜索dsi tuner

http://www.ti.com.cn/tool/cn/dsi-tuner?keyMatch=dsi%20tuner&tisearch=Search-CN-Everything

工具使用

上述参数填好以后,左上角第二个设置按钮点开,生成你需要的CRS.txt

以上,是整个Bringup流程

猜你喜欢

转载自blog.csdn.net/u012839187/article/details/84679976
TI
今日推荐