linux简单驱动程序驱动CH455
- 在一次客户的项目中要求实现一个四位数码管的显示,数码管是正常的八段数码管,驱动ic是南京沁恒微的CH455,这款芯片有6个寄存器,其中四个用于驱动数码管显示,一个用于数码管的控制指令(亮度,显示位数等),还有一个用于键盘矩阵扫描,不过我们用不上这个,所以忽略掉这个寄存器。
- 这款芯片由于不是标准的i2c设备,我暂时没找到办法使用i2c控制器控制它(如果有好的办法控制欢迎指正),我本次分享的是使用gpio模拟i2c来控制芯片。
#ifndef _I2C_CH455_H
#define _I2C_CH455_H
#define I2C_DELAY 10
#define DEVINUM 1
#define LED1ON _IO('l', 10)
#define LED1OFF _IO('l', 11)
#define LED2ON _IO('l', 12)
#define LED2OFF _IO('l', 13)
#define NIEXIDATA _IOW('n', 12, unsigned short)
#define LEDNUM 4
#define SUCRES 0
#define ERRRES -100
#define CH455_BIT_ENABLE 0x01
#define CH455_BIT_SLEEP 0x04
#define CH455_BIT_7SEG 0x08
#define CH455_BIT_INTENS1 0x10
#define CH455_BIT_INTENS2 0x20
#define CH455_BIT_INTENS3 0x30
#define CH455_BIT_INTENS4 0x40
#define CH455_BIT_INTENS5 0x50
#define CH455_BIT_INTENS6 0x60
#define CH455_BIT_INTENS7 0x70
#define CH455_BIT_INTENS8 0x00
#define CH455_BIT_KOFF 0x80
#define CH455_DIG0 0x68
#define CH455_DIG1 0x6a
#define CH455_DIG2 0x6c
#define CH455_DIG3 0x6e
#define SYSTEM_CONFIG 0x48
#define Read_Key 0x4F
const unsigned char real_code[] = {0X3F, 0X06, 0X5B, 0X4F, 0X66, 0X6D, 0X7D, 0X07, 0X7F, 0X6F, 0X77, 0X7C, 0X58, 0X5E, 0X79, 0X71};
#endif
#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;
struct platform_device *device_ch455;
struct class *ch455_class;
};
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);
udelay(I2C_DELAY);
gpiod_set_value(ch455->scl_gpio, 0);
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);
udelay(I2C_DELAY);
gpiod_set_value(ch455->sda_gpio, 1);
udelay(I2C_DELAY);
}
static void CH455_I2c_WrByte(struct ch455_data *ch455, u8 IIC_Byte)
{
u8 i;
gpiod_direction_output(ch455->sda_gpio, 0);
for (i = 0; i < 8; i++)
{
if ((IIC_Byte & 0x80))
gpiod_set_value(ch455->sda_gpio, 1);
else
gpiod_set_value(ch455->sda_gpio, 0);
udelay(100);
gpiod_set_value(ch455->scl_gpio, 1);
udelay(100);
gpiod_set_value(ch455->scl_gpio, 0);
udelay(100);
IIC_Byte <<= 1;
}
}
#if 1
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
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);
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);
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;
temp[i] = 'h';
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;
}
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;
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)
{
int res;
u16 data;
struct ch455_data *ch455;
ch455 = filp->private_data;
if (!ch455)
goto err;
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;
}
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__);
return ERRRES;
}
static struct file_operations ch455_opr = {
.owner = THIS_MODULE,
.open = ch455_open,
.unlocked_ioctl = ch455_ioctl,
};
static ssize_t ch455_dev_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
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);
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);
ch455_write_data(ch455, ((SYSTEM_CONFIG << 8) | 0x01));
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");
+ ch455 {
+ status = "okay";
+ compatible = "test,ch455";
+
+
+ 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>;
+ };
- 上诉驱动中实现了sys接口可以直接使用sys进行debug和java上层应用调用,设备树中pinctl信息需要按照自己使用soc平台设置方法进行配置,gpio需要查看对应的原理图信息。下图是传输0xff的时候示波器抓取的一帧数据,已经正确发出。
- 操作方法
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