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流程