linux简单驱动程序驱动CH455

linux简单驱动程序驱动CH455

  1. 在一次客户的项目中要求实现一个四位数码管的显示,数码管是正常的八段数码管,驱动ic是南京沁恒微的CH455,这款芯片有6个寄存器,其中四个用于驱动数码管显示,一个用于数码管的控制指令(亮度,显示位数等),还有一个用于键盘矩阵扫描,不过我们用不上这个,所以忽略掉这个寄存器。
    linux简单驱动程序驱动CH455_第1张图片
  2. 这款芯片由于不是标准的i2c设备,我暂时没找到办法使用i2c控制器控制它(如果有好的办法控制欢迎指正),我本次分享的是使用gpio模拟i2c来控制芯片。
  • i2c-ch455.h
#ifndef _I2C_CH455_H
#define _I2C_CH455_H
#define I2C_DELAY 10
#define DEVINUM 1
#define LED1ON _IO('l', 10)  /* led1on */
#define LED1OFF _IO('l', 11) /* led2off */
#define LED2ON _IO('l', 12)  /* led2on */
#define LED2OFF _IO('l', 13) /* led2off */
#define NIEXIDATA _IOW('n', 12, unsigned short)
#define LEDNUM 4 /* 数码管数量 */
/* return value */
#define SUCRES 0
#define ERRRES -100

/* 系统参数命令 */
#define CH455_BIT_ENABLE	0x01		// 开启/关闭位
#define CH455_BIT_SLEEP		0x04		// 睡眠控制位
#define CH455_BIT_7SEG		0x08		// 7段控制位
#define CH455_BIT_INTENS1	0x10		// 1级亮度
#define CH455_BIT_INTENS2	0x20		// 2级亮度
#define CH455_BIT_INTENS3	0x30		// 3级亮度
#define CH455_BIT_INTENS4	0x40		// 4级亮度
#define CH455_BIT_INTENS5	0x50		// 5级亮度
#define CH455_BIT_INTENS6	0x60		// 6级亮度
#define CH455_BIT_INTENS7	0x70		// 7级亮度
#define CH455_BIT_INTENS8	0x00		// 8级亮度
#define CH455_BIT_KOFF    0x80    // 为 1则关闭按键扫描

#define CH455_DIG0  0x68
#define CH455_DIG1  0x6a
#define CH455_DIG2  0x6c
#define CH455_DIG3  0x6e

#define SYSTEM_CONFIG  0x48
#define Read_Key       0x4F
/* 0 ~ f */
const unsigned char real_code[] = {0X3F, 0X06, 0X5B, 0X4F, 0X66, 0X6D, 0X7D, 0X07, 0X7F, 0X6F, 0X77, 0X7C, 0X58, 0X5E, 0X79, 0X71};
#endif
  • i2c-ch455.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "i2c-ch455.h"

/* 私有数据结构体 */
struct ch455_data
{
    dev_t devnum;
    spinlock_t lock; /* 资源锁 */
    struct cdev ch455_cdev;
    struct gpio_desc *sda_gpio;
    struct gpio_desc *scl_gpio;
    struct gpio_desc *led1_gpio;
    struct gpio_desc *led2_gpio;
    struct device *ch455_fops_device;     /* fops */
    struct platform_device *device_ch455; /* platform */
    struct class *ch455_class;
    // struct i2c_client *ch455_client;
};
/* 传输函数 */
static void CH455_I2c_Start(struct ch455_data *ch455)
{
    gpiod_direction_output(ch455->sda_gpio, 1);
    udelay(I2C_DELAY);
    gpiod_direction_output(ch455->scl_gpio, 1);
    udelay(I2C_DELAY);
    gpiod_set_value(ch455->sda_gpio, 0); // ch455->sda_gpio = low;
    udelay(I2C_DELAY);
    gpiod_set_value(ch455->scl_gpio, 0); // ch455->scl_gpio = low;
    udelay(I2C_DELAY);
}

static void CH455_I2c_Stop(struct ch455_data *ch455)
{
    gpiod_direction_output(ch455->scl_gpio, 0);
    udelay(I2C_DELAY);
    gpiod_direction_output(ch455->sda_gpio, 0);
    udelay(I2C_DELAY);
    gpiod_set_value(ch455->scl_gpio, 1); // ch455->scl_gpio = high;
    udelay(I2C_DELAY);
    gpiod_set_value(ch455->sda_gpio, 1); // ch455->sda_gpio = high;
    udelay(I2C_DELAY);
}
static void CH455_I2c_WrByte(struct ch455_data *ch455, u8 IIC_Byte)
{
    u8 i;
    /* 先拉低数据为等待写入数据 */
	//pr_err("*** func:%s line:%d IIC_Byte:%#x ***\n", __func__, __LINE__, IIC_Byte);
    gpiod_direction_output(ch455->sda_gpio, 0);
    for (i = 0; i < 8; i++)
    {
        if ((IIC_Byte & 0x80))
            gpiod_set_value(ch455->sda_gpio, 1); // ch455->sda_gpio=high;
        else
            gpiod_set_value(ch455->sda_gpio, 0); // ch455->sda_gpio=low;
        udelay(100);
        gpiod_set_value(ch455->scl_gpio, 1); // ch455->scl_gpio=high;
        udelay(100);
        gpiod_set_value(ch455->scl_gpio, 0); // ch455->scl_gpio=low;
        udelay(100);
        IIC_Byte <<= 1;
    }
}
#if 1
/* 产生一个ACK */
static void CH455_I2c_Send_ACK(struct ch455_data *ch455)
{
    gpiod_direction_output(ch455->scl_gpio, 0);
    udelay(I2C_DELAY);
    gpiod_direction_output(ch455->sda_gpio, 0);
    udelay(I2C_DELAY);
    gpiod_set_value(ch455->scl_gpio, 1);
    udelay(I2C_DELAY);
    gpiod_set_value(ch455->scl_gpio, 0);
}
#endif
#if 0
/* 释放sda,等待ack信号 */
static u8 CH455_I2c_Wait_ACK(struct ch455_data *ch455)
{
    if (!ch455)
        return ERRRES;
    gpiod_direction_output(ch455->sda_gpio, 1);
    udelay(1);
    gpiod_direction_output(ch455->scl_gpio, 1);
    udelay(1);
    gpiod_direction_input(ch455->sda_gpio);
	udelay(300);
    /* 如果被拉低代表回应一个ack信号 */
    if (gpiod_get_value(ch455->sda_gpio))
    {
		pr_err("func:%s line:%d sda_stat:%d\n", __func__, __LINE__, gpiod_get_value(ch455->sda_gpio));
        CH455_I2c_Stop(ch455);
        return ERRRES;
    }
    gpiod_direction_output(ch455->scl_gpio, 0);
    return SUCRES;
}
#endif
static int ch455_write_data(struct ch455_data *ch455, u16 data)
{
    int i;
	unsigned long flags;
    if (!ch455)
        return ERRRES;
    pr_err("func:%s, line:%d, data:%#x\n", __func__, __LINE__, data);
	spin_lock_irqsave(&ch455->lock, flags);
    CH455_I2c_Start(ch455);
    /* data = cmd1 | cmd2 */
    for (i = 1; i >= 0; i--)
    {
        CH455_I2c_WrByte(ch455, (u8)(data >> (i * 8)));
#if 0
        res = CH455_I2c_Wait_ACK(ch455);
		if(res < 0) {
			pr_err("func:%s: i2c transfer failed\n", __func__);
			return ERRRES;
		}
#endif
		CH455_I2c_Send_ACK(ch455);
    }
    CH455_I2c_Stop(ch455);
	spin_unlock_irqrestore(&ch455->lock, flags);
    pr_err("func:%s, line:%d\n", __func__, __LINE__);
    return SUCRES;
}
static int show_data(struct ch455_data *ch455, u8 *data)
{
    int i;
	int j;
    int res = LEDNUM;
    u8 *buf;
    u8 *temp;
	u8 DIG[] = {CH455_DIG0, CH455_DIG1, CH455_DIG2, CH455_DIG3};
    /* 判断传入的值的数量 */
    (res < strlen(data)) ? (res = strlen(data)) : res;
    buf = kzalloc(res, GFP_KERNEL);
    temp = kzalloc(res, GFP_KERNEL);
    memset(buf, 0, res);
    memset(temp, 0, res);
    memcpy(temp, data, res);
    for (i = 0; i < res; i++)
    {
        if (temp[i] >= '0' && temp[i] <= '9') 
			buf[i] = real_code[(data[i] - '0')];
        else if (temp[i] >= 'a' && temp[i] <= 'f') 
			buf[i] = real_code[(data[i] - 'a' + 10)];
        else if (temp[i] == '.')
        {
            buf[i - 1] |= 0x80; /* 第八段是seg7控制的,如果上一位有点,数据最高位与上一个1就行 */
            temp[i] = 'h';        /* 超过f就行 */
            i--;
        }
        else
            continue;
    }
	j = 0;
    for (i = 0; i < res; i++)
    {
        if (buf[i])
        {
            if (ch455_write_data(ch455, ((DIG[j++] << 8) | buf[i])) < 0)
                goto err_alloc;
        }
    }
    pr_err("*** func:%s line:%d res:%d ***\n", __func__, __LINE__, res);
    kfree(buf);
    kfree(temp);
    return SUCRES;
err_alloc:
    kfree(buf);
    pr_err("ch455 write data failed\n");
    kfree(temp);
    return ERRRES;
}
/* 控制led */
static int ctr_led(struct ch455_data *ch455, u8 *data)
{
    unsigned long flags;
    if (!ch455 || !data || strlen(data) < 6)
    {
        return ERRRES;
    }
    spin_lock_irqsave(&ch455->lock, flags);
    switch (data[3])
    {
    case '1':
        if ('n' == data[5])
        {
            gpiod_set_value(ch455->led1_gpio, 1);
        }
        else
        {
            gpiod_set_value(ch455->led1_gpio, 0);
        }
        break;
    case '2':
        if ('n' == data[5])
        {
            gpiod_set_value(ch455->led2_gpio, 1);
        }
        else
        {
            gpiod_set_value(ch455->led2_gpio, 0);
        }
        break;
    default:
        break;
    }
    spin_unlock_irqrestore(&ch455->lock, flags);
    return SUCRES;
}
static int ch455_open(struct inode *node, struct file *filp)
{
    struct ch455_data *ch455;
    ch455 = container_of(node->i_cdev, struct ch455_data, ch455_cdev);
    if (IS_ERR(ch455))
        goto err;
    filp->private_data = ch455; /* 保存数据到文件结构体file链表中 */
    return SUCRES;
err:
    pr_err("%s: set ch455 drvdata faile\n", __func__);
    return PTR_ERR(ch455);
}
static long ch455_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    /* 用于初始化led,传入打印数值, 控制led灯亮灭 */
    int res;
    u16 data;
	//unsigned long flags;
    struct ch455_data *ch455;
    ch455 = filp->private_data;
    if (!ch455)
        goto err;
	//spin_lock_irqsave(&ch455->lock, flags);
    switch (cmd)
    {
    case LED1ON:
        res = ctr_led(ch455, "led1on");
        if (res < 0)
            goto err;
        break;
    case LED1OFF:
        res = ctr_led(ch455, "led1off");
        if (res < 0)
            goto err;
        break;
    case LED2ON:
        res = ctr_led(ch455, "led2on");
        if (res < 0)
            goto err;
        break;
    case LED2OFF:
        res = ctr_led(ch455, "led2off");
        if (res < 0)
            goto err;
        break;
    case NIEXIDATA:
        if (copy_from_user(&data, (u16 *)arg, sizeof(u16)))
            goto err_copy;
        res = ch455_write_data(ch455, data);
        if (res < 0)
            goto err;
    default:
        break;
    }
	//spin_unlock_irqrestore(&ch455->lock, flags);
    return SUCRES;

err_copy:
    pr_err("copy from user failed!\n");
err:
    if (!ch455)
        pr_err("%s: get ch455 drvdata faile\n", __func__);
    if (res < 0)
        pr_err("%s: ch455 write data faile\n", __func__);
	//spin_unlock_irqrestore(&ch455->lock, flags);
    return ERRRES;
}

static struct file_operations ch455_opr = {
    .owner = THIS_MODULE,
    .open = ch455_open,
    .unlocked_ioctl = ch455_ioctl,
};
/* sys debug */
static ssize_t ch455_dev_store(struct device *dev,
                               struct device_attribute *attr, const char *buf,
                               size_t count)
{
    /* echo值进来直接显示,或者控制led亮灭 */
    int res;
    char port_buf[5];
    char value_buf[4];
    struct ch455_data *ch455;
    struct platform_device *ch455_client;
    pr_err("*** func:%s line:%d buf:%s count:%ld ***\n", __func__, __LINE__,
           buf, count);
    ch455_client = container_of(dev, struct platform_device, dev);
    if (IS_ERR(ch455_client))
        goto err;
    ch455 = platform_get_drvdata(ch455_client);
    if (IS_ERR(ch455))
        goto err;
    if (count < 9 || buf[3] != ' ')
    {
        pr_err("<%s> invalid write string: %s\n", __func__, buf);
        pr_err("example: echo \"smg\" \"1234\" > xxx/ch455_dev\n");
        pr_err("example: echo \"led\" \"led1on\" > xxx/ch455_dev\n");
        return ERRRES;
    }
    sscanf(buf, "%s %s", port_buf, value_buf);
    pr_err(" func:%s line:%d buf[0]:%c \n", __func__, __LINE__,
           port_buf[0]);
    switch (port_buf[0])
    {
    case 's':
        pr_err(" func:%s line:%d \n", __func__, __LINE__);
        res = show_data(ch455, value_buf);
        break;
    case 'l':
        pr_err(" func:%s line:%d \n", __func__, __LINE__);
        res = ctr_led(ch455, value_buf);
        break;
    default:
        break;
    }
    pr_err("*** func:%s line:%d ***\n", __func__, __LINE__);
    return (res > 0) ? res : ERRRES;

err:
    pr_err("cannot get drvdate\n");
    if (IS_ERR(ch455_client))
        return PTR_ERR(ch455_client);
    else
        return PTR_ERR(ch455);
}
static DEVICE_ATTR(ch455_dev, S_IWUSR | S_IWGRP, NULL, ch455_dev_store);
static CLASS_ATTR_STRING(version, S_IRUGO, "CH455 1.0.0 20231022");
static int ch455_probe(struct platform_device *ch455_dev)
{
    int i;
    int res;
    int major;
    dev_t devnum;
    struct device *ch455device;
    struct ch455_data *ch455;
    pr_err("*** func:%s line:%d ***\n", __func__, __LINE__);
    ch455 = devm_kzalloc(&ch455_dev->dev, sizeof(struct ch455_data),
                         GFP_KERNEL);
    if (IS_ERR(ch455))
        goto err_kzalloc;
    major = MAJOR(devnum);
    ch455->device_ch455 = ch455_dev;
    ch455device = ch455->ch455_fops_device;
    /* 完成字符设备驱动的注册 */
    cdev_init(&ch455->ch455_cdev, &ch455_opr);
    res = cdev_add(&ch455->ch455_cdev, MKDEV(major, 0), DEVINUM);
    if (res)
        return ERRRES;
    ch455->ch455_class = class_create(THIS_MODULE, "ch455");
    if (IS_ERR(ch455->ch455_class))
        goto err_class;
    res = class_create_file(ch455->ch455_class, &class_attr_version.attr);
    if (res)
        goto err_create_file;
    res = alloc_chrdev_region(&devnum, 0, DEVINUM, "ch455");
    if (res)
    {
        goto err_alloc_cr;
    }

    for (i = 0; i < DEVINUM; i++)
    {
        ch455->ch455_fops_device =
            device_create(ch455->ch455_class, NULL, MKDEV(major, i),
                          NULL, "ch455.%d", i);
        if (IS_ERR(ch455->ch455_fops_device))
            goto err_device_create;
    }
    res = device_create_file(&ch455_dev->dev, &dev_attr_ch455_dev);
    if (res)
        goto err_device_create;
    spin_lock_init(&ch455->lock);
    platform_set_drvdata(ch455_dev, ch455);
    /* 获取gpio */
    ch455->scl_gpio = devm_gpiod_get_optional(&ch455_dev->dev, "scl", 0);
    if (IS_ERR(ch455->scl_gpio))
    {
        pr_err("devm gpiod get opt failed\n");
        goto err_device_create;
    }
    ch455->sda_gpio = devm_gpiod_get_optional(&ch455_dev->dev, "sda", 0);
    if (IS_ERR(ch455->sda_gpio))
    {
        pr_err("devm gpiod get opt failed\n");
        goto err_device_create;
    }
    ch455->led1_gpio = devm_gpiod_get_optional(&ch455_dev->dev, "led1", 0);
    if (IS_ERR(ch455->led1_gpio))
    {
        pr_err("devm gpiod get opt failed\n");
        goto err_device_create;
    }
    ch455->led2_gpio = devm_gpiod_get_optional(&ch455_dev->dev, "led2", 0);
    if (IS_ERR(ch455->led2_gpio))
    {
        pr_err("devm gpiod get opt failed\n");
        goto err_device_create;
    }
    /* 这里是不是需要加个延时 */
    gpiod_direction_output(ch455->led1_gpio, 0);
    gpiod_direction_output(ch455->led2_gpio, 0);
    /* test */
	/* 打开数码管八段显示 */
	ch455_write_data(ch455, ((SYSTEM_CONFIG << 8) | 0x01));
	//ch455_write_data(ch455, ((CH455_DIG0 << 8) | 0xFF));
	//ch455_write_data(ch455, ((CH455_DIG1 << 8) | 0xFF));
	//ch455_write_data(ch455, ((CH455_DIG2 << 8) | 0xFF));
	//ch455_write_data(ch455, ((CH455_DIG3 << 8) | 0xFF));
    pr_err("*** func:%s line:%d ***\n", __func__, __LINE__);
    return SUCRES;

err_device_create:
    for (--i; i >= 0; i--)
        device_destroy(ch455->ch455_class, MKDEV(major, i));
    unregister_chrdev_region(devnum, DEVINUM);
err_alloc_cr:
    class_remove_file(ch455->ch455_class, &class_attr_version.attr);
err_create_file:
    class_destroy(ch455->ch455_class);
err_class:
    cdev_del(&ch455->ch455_cdev);
err_kzalloc:
    if (IS_ERR(ch455))
    {
        pr_err("ch455 set pri data failed!\n");
        return PTR_ERR(ch455);
    }
    return ERRRES;
}
static int ch455_remove(struct platform_device *ch455_dev)
{
    int i;
    int major;
    struct ch455_data *ch455;
    ch455 = platform_get_drvdata(ch455_dev);
    if (IS_ERR(ch455))
        goto err;
    major = MAJOR(ch455->devnum);
    device_remove_file(ch455->ch455_fops_device, &dev_attr_ch455_dev);
    for (--i; i >= 0; i--)
        device_destroy(ch455->ch455_class, MKDEV(major, i));
    cdev_del(&ch455->ch455_cdev);
    unregister_chrdev_region(ch455->devnum, DEVINUM);
    class_remove_file(ch455->ch455_class, &class_attr_version.attr);
    class_destroy(ch455->ch455_class);
    return 0;

err:
    pr_err("i2c get drvdate failed!\n");
    return PTR_ERR(ch455);
}
static struct of_device_id ch455_id[] = {
    {
        .compatible = "test,ch455",
    },
    {},
};
static struct platform_driver ch455_dri = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "ch455_driver",
        .of_match_table = ch455_id,
    },
    .probe = ch455_probe,
    .remove = ch455_remove,
};

static int __init ch455_init(void)
{
    int res;
    res = platform_driver_register(&ch455_dri);
    if (res)
        return ERRRES;
    return SUCRES;
}
static void __exit ch455_exit(void)
{
    platform_driver_unregister(&ch455_dri);
}
module_init(ch455_init);
module_exit(ch455_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dancehall");
  • dts配置
+       ch455 {
+               status = "okay";
+               compatible = "test,ch455";
+               #address-cells = <1>;
+               #size-cells = <0>;
+               sda-gpio = <&gpio0 RK_PB4 GPIO_ACTIVE_HIGH>;/* gpio0_b4 */
+               scl-gpio = <&gpio0 RK_PB3 GPIO_ACTIVE_HIGH>;/* gpio0_b3 */
+               led1-gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;/* led1 gpio0_b6 */
+               led2-gpio = <&gpio0 RK_PB5 GPIO_ACTIVE_HIGH>;/* led1 gpio0_b5 */
+               pinctrl-names = "default";
+               pinctrl-0 = <&ch455_pins>;
+               pinctrl-1 = <&led_pins>;
+       };
  1. 上诉驱动中实现了sys接口可以直接使用sys进行debug和java上层应用调用,设备树中pinctl信息需要按照自己使用soc平台设置方法进行配置,gpio需要查看对应的原理图信息。下图是传输0xff的时候示波器抓取的一帧数据,已经正确发出。
    linux简单驱动程序驱动CH455_第2张图片
    linux简单驱动程序驱动CH455_第3张图片
  2. 操作方法
echo led led1on > /sys/devices/platform/ch455/ch455_dev
echo led led1off > /sys/devices/platform/ch455/ch455_dev
echo led led2on > /sys/devices/platform/ch455/ch455_dev
echo led led2off > /sys/devices/platform/ch455/ch455_dev
echo smg 1234 > /sys/devices/platform/ch455/ch455_dev
echo smg 1.234 > /sys/devices/platform/ch455/ch455_dev

你可能感兴趣的:(linux)