ftdi_sio驱动学习笔记 7 - 增加MPSSE I2C

目录

1. 初始化

2. 注销

3. ftdi_mpsse_i2c_algo

3.1 ftdi_mpsse_functionality

3.2 ftdi_mpsse_i2c_xfer

4. ftdi_mpsse_i2c_quirks

5. MPSSE I2C协议宏定义

6. I2C协议函数

6.1 i2c_start

6.2 i2c_stop

6.3 i2c_receive_byte

6.4 i2c_send_byte

7. ftdi_mpsse_i2c_read

8. ftdi_mpsse_i2c_write

9. ftdi_mpsse_i2c_write_read


对于MPSSE,如果采用纯GPIO模拟的方式实现,可以避免使用前面3个IO,从而实现SPI和I2C的组合使用,而且可以分配多个i2c通道。这里采用这种方式。比如FT232H只有一路MPSSE通道,那么一共16个GPIO,其中前面3个IO作为SPI使用,单了一个GPIO,干脆分配4个IO给SPI,而其他12个IO可以组合成6个I2C接口。FT2232H可以当作2个FT2232H,而FT4232H虽然也是2路MPSSE,但是一路MPSSE只能8个IO,所以一路最多支持2个I2C接口。

在ftdi_private中添加i2c使用到的私有变量

#ifdef CONFIG_I2C
	struct i2c_adapter *i2c_adap;
	int i2c_adap_num;
	int i2c_adap_start;
	u8 *i2c_command;
	int i2c_command_index;
	u8 i2c_clk;
#endif

1. 初始化

和gpio类似,在ftdi_port_probe里面gpio初始化后面添加i2c的初始化

result = ftdi_mpsse_i2c_init(port);
if (result < 0) {
	dev_err(&port->serial->interface->dev,
		"I2C initialisation failed: %d\n",
		result);
}

在ftdi_mpsse_i2c_init里面首先根据芯片分配i2c适配器数量

switch (priv->chip_type) {
	case FT232H:
	case FT232HP:
	case FT233HP:
	case FT2232H:
	case FT2232HP:
	case FT2233HP:
		priv->i2c_adap_num = 6;
		break;
	case FT4232H:
	case FT4232HP:
	case FT4233HP:
	case FT4232HA:
		priv->i2c_adap_num = 2;
		break;
	default:
		return -ENODEV;
}
priv->i2c_adap = kmalloc(sizeof(struct i2c_adapter) * priv->i2c_adap_num, GFP_KERNEL);
if(priv->i2c_adap == NULL) {
	return -ENOMEM;
}

初始化i2c时钟,GPIO模拟的方式频率并不快,默认频率设置为1

priv->i2c_clk = 1;

然后初始化这些i2c适配器,并添加到系统。

for(i = 0; i < priv->i2c_adap_num; i++){
	priv->i2c_adap[i].nr = i;
	priv->i2c_adap[i].owner = THIS_MODULE;
	priv->i2c_adap[i].class = I2C_CLASS_HWMON;
	priv->i2c_adap[i].algo = &ftdi_mpsse_i2c_algo;
	priv->i2c_adap[i].quirks = &ftdi_mpsse_i2c_quirks;
	priv->i2c_adap[i].dev.parent = &port->dev;
	snprintf(priv->i2c_adap[i].name, sizeof(priv->i2c_adap[i].name),
		"FT-H usb-i2c%d bridge", i);

	i2c_set_adapdata(&priv->i2c_adap[i], port);
	i2c_add_adapter(&priv->i2c_adap[i]);
}

最后记录i2c的起始编号,这样可以得到实际的通道编号ch。

priv->i2c_adap_start = priv->i2c_adap[0].nr;

2. 注销

在ftdi_port_remove中添加ftdi_mpsse_i2c_remove,这个函数将i2c的资源释放掉。

if(priv->i2c_adap) {
    int i;
    for(i = 0; i < priv->i2c_adap_num; i++){
        i2c_del_adapter(&priv->i2c_adap[i]);
    }
	
    kfree(priv->i2c_adap);
}

3. ftdi_mpsse_i2c_algo

static const struct i2c_algorithm ftdi_mpsse_i2c_algo = {
	.master_xfer = ftdi_mpsse_i2c_xfer,
	.functionality = ftdi_mpsse_functionality,
};

这部分是i2c的核心部分,通过这个结构体注册好实现i2c通信部分。

3.1 ftdi_mpsse_functionality

static u32 ftdi_mpsse_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
	    I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
	    I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
}

该函数用于设置i2c适配器的功能。返回值是一个位掩码,表示支持的I2C和SMBus操作类型。

具体支持的功能如下:

  • I2C_FUNC_I2C:支持标准的I2C操作
  • I2C_FUNC_SMBUS_BYTE:支持SMBus字节操作
  • I2C_FUNC_SMBUS_BYTE_DATA:支持SMBus字节数据操作
  • I2C_FUNC_SMBUS_WORD_DATA:支持SMBus双字节数据操作
  • I2C_FUNC_SMBUS_BLOCK_DATA:支持SMBus块数据操作
  • I2C_FUNC_SMBUS_I2C_BLOCK:支持SMBus I2C块操作

3.2 ftdi_mpsse_i2c_xfer

这个函数是i2c读写的实现。首先要从适配器变量中获取port和priv指针。

struct usb_serial_port *port = i2c_get_adapdata(adapter);
struct ftdi_private *priv = usb_get_serial_port_data(port);

由于i2c也是通过gpio的方式实现,所以这里也通过gpio_lock实现锁。

mutex_lock(&priv->gpio_lock);

//i2c的处理

i2c_exit:
	mutex_unlock(&priv->gpio_lock);

芯片需要进入mpsse模式才能进行i2c通信,所以先判断一下是否在mpsse模式,如果不是则进入mpsse模式。

if(priv->bitmode != FTDI_SIO_BITMODE_MPSSE)
    ret = ftdi_set_mpsse_mode(port);
if(ret < 0){
    dev_err(&port->dev, "ftdi_set_mpsse_mode failed\n");
    goto i2c_exit;
}

 由于没有退出mpsse的机制,所以在ftdi_open里面,在原来的处理前添加退出mpsse的机制:

if(priv->gpio_used) {
	return -EBUSY;
} else if(priv->bitmode != FTDI_SIO_BITMODE_RESET) {
	ftdi_set_bitmode(port, (priv->gpio_output << 4) | priv->gpio_value, FTDI_SIO_BITMODE_RESET);
}

也就是如果用户打开串口,则会导致mpsse模式退出,当然,这里也增加了如果gpio被使用了,那么打开串口也会失败。可以先export一个gpio,然后用串口工具打开设备,此时就会提示错误。

接着就是根据不同的命令跳转到不同的处理函数

if (num == 1) {
	if (msgs->flags & I2C_M_RD)
		ret = ftdi_mpsse_i2c_read(port, adapter->nr - priv->i2c_adap_start, msgs->addr, msgs->buf, msgs->len);
	else
		ret = ftdi_mpsse_i2c_write(port, adapter->nr - priv->i2c_adap_start, msgs->addr, msgs->buf, msgs->len);
	if (ret < 0)
		goto i2c_exit;
} else {
	/* Combined write then read message */
	ret = ftdi_mpsse_i2c_write_read(port, adapter->nr - priv->i2c_adap_start, msgs);
	if (ret < 0)
		goto i2c_exit;
}
ret = num;

三个具体的实现后面再说明。

4. ftdi_mpsse_i2c_quirks

这个结构体用于描述I2C适配器的特殊行为。I2C_AQ_COMB_WRITE_THEN_READ标志表示适配器支持在一次操作中先写入数据再读取数据的组合操作,而max_comb_1st_msg_len字段指定了在组合操作中第一次消息的最大长度为2字节。

5. MPSSE I2C协议宏定义

定义GPIO起始编号

#define I2C_GPIO_START_INDEX    4

定义每组I2C对应的SCL和SDA的编号

#define I2C_SCL_INDEX(ch)       (ch * 2 + I2C_GPIO_START_INDEX)
#define I2C_SDA_INDEX(ch)       (ch * 2 + I2C_GPIO_START_INDEX + 1) 

定义命令缓存更新的宏定义

#define gpioCommand(ch) (I2C_SCL_INDEX(ch) / 8)
#define i2cCommandWrite(priv, ch, n)    do{\
    int i;\
    for (i = 0; i < (int)priv->i2c_clk * n; i++)\
    {\
        priv->i2c_command[priv->i2c_command_index++] = gpioWriteCommand[gpioCommand(ch)];\
        priv->i2c_command[priv->i2c_command_index++] = (priv->mpsse_gpio_value >> (gpioCommand(ch) * 8)) & 0xff;\
        priv->i2c_command[priv->i2c_command_index++] = (priv->mpsse_gpio_output >> (gpioCommand(ch) * 8)) & 0xff;\
    }\
}while(0)

gpioCommand(ch)是为了获取到该组I2C对应的GPIO在低位还是在高位字节中,返回0或1。

而i2cCommandWrite是写入命令到命令缓冲中。

const uint8_t gpioWriteCommand[2] = {0x80, 0x82};
const uint8_t gpioReadCommand[2] = {0x81, 0x83};

SDA的操作宏定义:

#define i2cSDAOut(priv, ch)     do{\
    priv->mpsse_gpio_output |= ((u16)1 << I2C_SDA_INDEX(ch));\
}while(0)

#define i2cSDAIn(priv, ch)      do{\
    priv->mpsse_gpio_output &= (u16)(~((u16)1 << I2C_SDA_INDEX(ch)));\
}while(0)

#define i2cSDAHigh(priv, ch)    do{\
    priv->mpsse_gpio_value |= ((u16)1 << I2C_SDA_INDEX(ch));\
}while(0)

#define i2cSDALow(priv, ch)     do{\
    priv->mpsse_gpio_value &= (u16)(~((u16)1 << I2C_SDA_INDEX(ch)));\
}while(0)

#define i2cSDARead(priv, ch)    do{\
    priv->i2c_command[priv->i2c_command_index++] = gpioReadCommand[gpioCommand(ch)];\
}while(0)

SCL的操作宏定义:

#define i2cSCLHigh(priv, ch)    do{\
    priv->mpsse_gpio_value |= ((u16)1 << I2C_SCL_INDEX(ch));\
}while(0)

#define i2cSCLLow(priv, ch)     do{\
    priv->mpsse_gpio_value &= (u16)(~((u16)1 << I2C_SCL_INDEX(ch)));\
}while(0)

6. I2C协议函数

6.1 i2c_start

初始化SCL和SDA为输出高电平,然后在SCL为高电平的情况下,SDA产生一个下降沿产生start信号。

static void i2c_start(struct usb_serial_port *port, u8 ch)
{
    struct ftdi_private *priv = usb_get_serial_port_data(port);
    priv->mpsse_gpio_value |= ((u16)1 << I2C_SDA_INDEX(ch)) | ((u16)1 << I2C_SCL_INDEX(ch));
    priv->mpsse_gpio_output |= ((u16)1 << I2C_SDA_INDEX(ch)) | ((u16)1 << I2C_SCL_INDEX(ch));
    //Set SDA output
    //SCL outputs high, SDA outputs high
    i2cSDAOut(priv, ch);
    i2cSCLHigh(priv, ch);
    i2cSDAHigh(priv, ch);
    i2cCommandWrite(priv, ch, 1);

    //SDA outputs low
    i2cSDALow(priv, ch);
    i2cCommandWrite(priv, ch, 2);           
}

计算一下这个stop需要多少个字节命令缓存

#define I2C_START_CMD_LEN           (3 * 3 * priv->i2c_clk)

6.2 i2c_stop

在SCL为高电平的情况下,SDA产生一个上降沿产生stop信号。

#define I2C_STOP_CMD_LEN           (3 * 3 * priv->i2c_clk)

static void i2c_stop(struct usb_serial_port *port, u8 ch)
{
    struct ftdi_private *priv = usb_get_serial_port_data(port);
    //SDA outpus low
    i2cSDALow(priv, ch);
    i2cCommandWrite(priv, ch, 1);
    
    //SCL outputs high
    i2cSCLHigh(priv, ch);
    i2cCommandWrite(priv, ch, 1);
    
    //SDA outputs high
    i2cSDAHigh(priv, ch);
    i2cCommandWrite(priv, ch, 1);
}

6.3 i2c_receive_byte

这个函数的功能是接收一个字节数据,接收完8个位的数据后(实际是读入对应GPIO所在的8位接口,相当于读入了8个字节数据,在后面从device返回的数据中再根据对应的位组合成需要的1个字节数据),要根据是否需要发送ack信号控制SDA输出。

#define I2C_RECV_CMD_LEN           (5 * 3 * priv->i2c_clk + 8 * (2 * 3 * priv->i2c_clk + 1) + 1)
static void i2c_receive_byte(struct usb_serial_port *port, u8 ch, bool ack)
{
    struct ftdi_private *priv = usb_get_serial_port_data(port);
    //SCL outputs low, SDA outputs low
    //Set SDA as input
    i2cSCLLow(priv, ch);
    i2cSDALow(priv, ch);
    i2cSDAIn(priv, ch);
    i2cCommandWrite(priv, ch, 1);

    u8 loop = 8;
    while (loop-- > 0) {
        //SCL output low
        i2cSCLLow(priv, ch);
        i2cCommandWrite(priv, ch, 1);

        //SCL output high
        i2cSCLHigh(priv, ch);
        i2cCommandWrite(priv, ch, 1);

        //Read SDA
        i2cSDARead(priv, ch);
    }

    //SCL outputs low, SDA outputs low
    //Set SDA as output
    i2cSCLLow(priv, ch);
    i2cSDALow(priv, ch);
    i2cSDAOut(priv, ch);
    i2cCommandWrite(priv, ch, 1);

    if (ack == true) {
        i2cSDALow(priv, ch);
    } else {
        i2cSDAHigh(priv, ch);
    }
    i2cCommandWrite(priv, ch, 1);

    //SCL output high
    i2cSCLHigh(priv, ch);
    i2cCommandWrite(priv, ch, 1);

    priv->i2c_command[priv->i2c_command_index++] = 0x87;

    //SCL output low
    i2cSCLLow(priv, ch);
    i2cCommandWrite(priv, ch, 1);
}

6.4 i2c_send_byte

这个函数的功能是发送一个字节数据,发送完8个位的数据后,需要读入SDA的状态判断ack信号,这里只是发送读入的命令,不实际判断ack信号。

#define I2C_SEND_CMD_LEN    (8 * 3 * (4 * priv->i2c_clk) + 12 * 3 * priv->i2c_clk + 1)
static void i2c_send_byte(struct usb_serial_port *port, u8 ch, u8 dat)
{
    struct ftdi_private *priv = usb_get_serial_port_data(port);

    for(u8 j = 0; j < 8; j++) {
        //SCL output low
        i2cSCLLow(priv, ch);
        i2cCommandWrite(priv, ch, 1);
        //SDA output dat
        if ((dat & 0x80) == 0x80)
            i2cSDAHigh(priv, ch);
        else
            i2cSDALow(priv, ch);
        dat <<= 1;
        //Set SDA as output
        i2cSDAOut(priv, ch);
        i2cCommandWrite(priv, ch, 1);
        //SCL output high
        i2cSCLHigh(priv, ch);
        i2cCommandWrite(priv, ch, 1);
    }

    //SCL outputs low, SDA outputs low
    //Set SDA as input
    i2cSCLLow(priv, ch);
    i2cSDALow(priv, ch);
    i2cSDAIn(priv, ch);
    i2cCommandWrite(priv, ch, 1);

    //SCL output low
    //i2cSCLLow(priv, ch);
    //i2cCommandWrite(priv, ch, 1);
    
    //SCL output high
    i2cSCLHigh(priv, ch);
    i2cCommandWrite(priv, ch, 10);
    //Read SDA
    i2cSDARead(priv, ch);

    //SDA outputs high
    //Set SDA as output
    i2cSDAHigh(priv, ch);
    i2cSDAOut(priv, ch);
    i2cCommandWrite(priv, ch, 1);

    //SCL output low
    i2cSCLLow(priv, ch);
    i2cCommandWrite(priv, ch, 1);
}

7. ftdi_mpsse_i2c_read

这个函数就是只有读部分,发送完i2c地址后就是直接读数据。

i2c_start(port, ch);	
i2c_send_byte(port, ch, (addr << 1) | 0x01);
while (--data_len > 0)
{
    i2c_receive_byte(port, ch, true);
}
i2c_receive_byte(port, ch, false);
i2c_stop(port, ch);

在配置这些命令前,需要先分配好命令缓存

size_t i2c_command_size = (I2C_START_CMD_LEN * 1 
    + I2C_SEND_CMD_LEN * 1
    + I2C_RECV_CMD_LEN * len
    + I2C_STOP_CMD_LEN);

priv->i2c_command_index = 0;
priv->i2c_command = kmalloc(i2c_command_size, GFP_KERNEL); 
if (!priv->i2c_command) {
    ret = -ENOMEM;
    return ret;
}

后面判断一下是否有内存泄漏

if(priv->i2c_command_index > i2c_command_size) {
    dev_err(&port->dev, "i2c_read_mem_err\n");
    ret = -ENOMEM;
    goto i2c_read_cmd_noenough;
}

然后配置好读数据的FIFO和缓冲,FIFO配置大一些,防止溢出。

priv->read_len = len * 8 + 1;
read_len = priv->read_len;
if(kfifo_alloc(&priv->read_fifo, priv->read_len * 2, GFP_KERNEL)) {
    dev_info(&port->dev, "ftdi_mpsse_i2c_read read buffer malloc fail\n");
    ret = -ENOMEM;
	goto i2c_read_cmd_noenough;
}

read_buffer = kmalloc(priv->read_len, GFP_KERNEL);
if(read_buffer == NULL) {
	dev_info(&port->dev, "ftdi_mpsse_i2c_read read buffer malloc fail\n");
    ret = -ENOMEM;
	goto i2c_read_fifo_noenough;
}

发送命令并等待返回的数据

ret = ftdi_usb_write(port, priv->i2c_command, priv->i2c_command_index);
if(ret < 0) {
    dev_err(&port->dev, "ftdi_usb_write failed\n");
    ret = -EIO;
    goto i2c_read_buf_noenough;
}
ret = wait_event_interruptible_timeout(priv->read_wait, !priv->read_len, HZ / 10); // 等待100 * n ms
if (ret == -ERESTARTSYS) {
    ret = -EINTR;// 信号中断睡眠
    goto i2c_read_buf_noenough;
} else if (!ret) {  // 超时
    dev_err(&port->dev, "Read pending timeout\n");
    priv->read_len = 0; // 防止死循环
    ret = -ETIMEDOUT;
    goto i2c_read_buf_noenough;
}

将返回的数据组合成实际的8位数据,这里不判断第一个地址是否ack

ret = kfifo_out(&priv->read_fifo, read_buffer, read_len);
int i;
int j = 0;
for(i = 0; i < len * 8; i++) {
    int max = i + 8;
    u8 tmp = 0;
    for(; i < max; i++) {
        tmp <<= 1;
        if ((read_buffer[i + 1] & ((u8)(1 << I2C_SDA_INDEX(ch)))) 
            == (u8)(1 << I2C_SDA_INDEX(ch))) {
            tmp |= 0x01;
        }
    }
    data[j++] = tmp;
    i--;
}

8. ftdi_mpsse_i2c_write

函数和ftdi_mpsse_i2c_read类似,区别是只写数据。

i2c_start(port, ch);
i2c_send_byte(port, ch, (addr << 1) | 0x00);
n = 0;	
while (data_len-- > 0){
    i2c_send_byte(port, ch, data[n++]);
}
i2c_stop(port, ch); 

其他部分类似ftdi_mpsse_i2c_read,只是返回的数据是每个写入字节的的ack信息

ret = kfifo_out(&priv->read_fifo, read_buffer, read_len);
for(int i = 0; i < len + 1; i++) {
    if ((read_buffer[i] & ((u8)(1 << I2C_SDA_INDEX(ch)))) 
        == (u8)(1 << I2C_SDA_INDEX(ch))) {  //Check ACK
        ret = -EIO;
        dev_err(&port->dev, "ACK error\n");
        goto i2c_write_buf_noenough;
    } 
}

9. ftdi_mpsse_i2c_write_read

这个函数是先写再读,实际应用中一般是先写设备的寄存器地址,然后再读数据,比ftdi_mpsse_i2c_read更常用。

i2c_start(port, ch);
i2c_send_byte(port, ch, (addr << 1) | 0x00);
n = 0;
while (wr_len-- > 0)
{
    i2c_send_byte(port, ch, msgs[0].buf[n++]);
}
i2c_start(port, ch);
i2c_send_byte(port, ch, (addr << 1) | 0x01);
while (--rd_len > 0)
{
    i2c_receive_byte(port, ch, true);
}
i2c_receive_byte(port, ch, false);
i2c_stop(port, ch);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值