BS8112A是一款带有12个按键的触摸芯片。I2C接口,带中断指示,可通过I2C配置寄存器改变按键的触发门槛值。
内核版本:Linux-4.15.0
硬件:荔枝派 licheepi-nano
一、在内核中新增BS8112A的驱动代码。在内核目录drivers/input/keyboard/下新建bs8112a.c,直接上代码
/*
* BS8112A - Touch keypad controler.
* data : 2019-12-24
* author : lutao
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BS8112A_CYCLE_INTERVAL (1*HZ/10) //100ms
static unsigned char bs8112a_key2code[] = {
KEY_0, KEY_1, KEY_2, KEY_3,
KEY_4, KEY_5, KEY_6, KEY_7,
KEY_8, KEY_9, KEY_A, KEY_B,
};
#define TRIGER_GATE_VAL 0x10
const unsigned char configbuffer[] =
{
0x00, //B0
0x00, //B1
0x83, //B2
0xf3, //B3
0xd8, //B4
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
TRIGER_GATE_VAL,
0x60, //C0
0x5e, //checksum
};
#define W_FLG 0
#define R_FLG 1
struct bs8112a_data {
struct i2c_client *client;
struct input_dev *input;
struct delayed_work dwork;
spinlock_t lock; /* Protects canceling/rescheduling of dwork */
unsigned short keycodes[ARRAY_SIZE(bs8112a_key2code)];
u16 key_matrix;
};
static char BS8112A_read_byte(struct i2c_client *client,const u8 reg);
static void BS8112A_write_byte(struct i2c_client *client,const u8 reg,const u8 val);
static int BS8112A_read_block(struct i2c_client *client,
u8 inireg, u8 *buffer, unsigned int count)
{
int error, idx = 0;
/*
* Can't use SMBus block data read. Check for I2C functionality to speed
* things up whenever possible. Otherwise we will be forced to read
* sequentially.
*/
if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
error = i2c_smbus_write_byte(client, inireg + idx);
if (error) {
dev_err(&client->dev,
"couldn't send request. Returned %d\n", error);
return error;
}
error = i2c_master_recv(client, buffer, count);
if (error != count) {
dev_err(&client->dev,
"couldn't read registers. Returned %d bytes\n", error);
return error;
}
} else {
while (count--) {
int data;
error = i2c_smbus_write_byte(client, inireg + idx);
if (error) {
dev_err(&client->dev,
"couldn't send request. Returned %d\n", error);
return error;
}
data = i2c_smbus_read_byte(client);
if (data < 0) {
dev_err(&client->dev,
"couldn't read register. Returned %d\n", data);
return data;
}
buffer[idx++] = data;
}
}
return 0;
}
static int BS8112A_get_key_matrix(struct bs8112a_data *bs8112)
{
struct i2c_client *client = bs8112->client;
struct input_dev *input = bs8112->input;
u8 regs[2];
u16 old_matrix, new_matrix;
u8 i;
int mask,keyval;
regs[0] = BS8112A_read_byte(client,0x08);
regs[1] = BS8112A_read_byte(client,0x09);
old_matrix = bs8112->key_matrix;
bs8112->key_matrix = new_matrix = (regs[1] << 8) | regs[0];
mask = 0x01;
for (i = 0; i < 12; ++i, mask <<= 1) {
keyval = new_matrix & mask;
if ((old_matrix & mask) != keyval) {
input_report_key(input, bs8112->keycodes[i], keyval);
//dev_dbg(&client->dev, "key %d %s\n", i, keyval ? "pressed" : "released");
}
}
input_sync(input);
return 0;
}
static irqreturn_t BS8112A_irq(int irq, void *_bs8112)
{
struct bs8112a_data *bs8112 = _bs8112;
unsigned long flags;
spin_lock_irqsave(&bs8112->lock, flags);
mod_delayed_work(system_wq, &bs8112->dwork, 0);
spin_unlock_irqrestore(&bs8112->lock, flags);
return IRQ_HANDLED;
}
static void BS8112A_schedule_read(struct bs8112a_data *bs8112)
{
spin_lock_irq(&bs8112->lock);
schedule_delayed_work(&bs8112->dwork, BS8112A_CYCLE_INTERVAL);
spin_unlock_irq(&bs8112->lock);
}
static void BS8112A_worker(struct work_struct *work)
{
struct bs8112a_data *bs8112 =
container_of(work, struct bs8112a_data, dwork.work);
dev_dbg(&bs8112->client->dev, "worker\n");
BS8112A_get_key_matrix(bs8112);
/* Avoid device lock up by checking every so often */
BS8112A_schedule_read(bs8112);
}
static void BS8112A_write_byte(struct i2c_client *client,const u8 reg,const u8 val)
{
char txbuf[2] = {reg,val};
struct i2c_msg msg[1] = {
[0] = {
.addr = client->addr,
.flags= W_FLG,
.len = sizeof(txbuf),
.buf = txbuf,
},
};
i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
}
static char BS8112A_read_byte(struct i2c_client *client,const u8 reg)
{
char txbuf[1] = {reg};
char rxbuf[1] = {0};
struct i2c_msg msg[2] = {
[0] = {
.addr = client->addr,
.flags = W_FLG,
.len = sizeof(txbuf),
.buf = txbuf,
},
[1] = {
.addr = client->addr,
.flags = I2C_M_RD,
.len = sizeof(rxbuf),
.buf = rxbuf,
},
};
i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
return rxbuf[0];
}
static int BS8112A_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct bs8112a_data *bs8112;
struct input_dev *input;
int i;
int error;
/* Check functionality */
error = i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE);
if (!error) {
dev_err(&client->dev, "%s adapter not supported\n",
dev_driver_string(&client->adapter->dev));
return -ENODEV;
}
/* Chip is valid and active. Allocate structure */
bs8112 = kzalloc(sizeof(struct bs8112a_data), GFP_KERNEL);
input = input_allocate_device();
if (!bs8112 || !input) {
dev_err(&client->dev, "insufficient memory\n");
error = -ENOMEM;
goto err_free_mem;
}
bs8112->client = client;
bs8112->input = input;
INIT_DELAYED_WORK(&bs8112->dwork, BS8112A_worker);
spin_lock_init(&bs8112->lock);
input->name = "BS8112A Touch Sense Keyboard";
input->id.bustype = BUS_I2C;
input->keycode = bs8112->keycodes;
input->keycodesize = sizeof(bs8112->keycodes[0]);
input->keycodemax = ARRAY_SIZE(bs8112a_key2code);
__set_bit(EV_KEY, input->evbit);
__clear_bit(EV_REP, input->evbit);
for (i = 0; i < ARRAY_SIZE(bs8112a_key2code); i++) {
bs8112->keycodes[i] = bs8112a_key2code[i];
__set_bit(bs8112a_key2code[i], input->keybit);
}
__clear_bit(KEY_RESERVED, input->keybit);
if (client->irq) {
error = request_irq(client->irq, BS8112A_irq, IRQF_TRIGGER_FALLING, "bs8112", bs8112);
if (error) {
dev_err(&client->dev,
"failed to allocate irq %d\n", client->irq);
goto err_free_mem;
}
}
error = input_register_device(bs8112->input);
if (error) {
dev_err(&client->dev,
"Failed to register input device\n");
goto err_free_irq;
}
i2c_set_clientdata(client, bs8112);
//printk("==i2c addr=%02x\n",client->addr);//0x50
//Init set config
for(i=0;iirq)
free_irq(client->irq, bs8112);
err_free_mem:
input_free_device(input);
kfree(bs8112);
return error;
}
static int BS8112A_remove(struct i2c_client *client)
{
struct bs8112a_data *bs8112 = i2c_get_clientdata(client);
/* Release IRQ so no queue will be scheduled */
if (client->irq)
free_irq(client->irq, bs8112);
cancel_delayed_work_sync(&bs8112->dwork);
input_unregister_device(bs8112->input);
kfree(bs8112);
return 0;
}
static const struct of_device_id BS8112A_of_match[] = {
{ .compatible = "BS8112A", },
{ },
};
static const struct i2c_device_id BS8112A_idtable[] = {
{ "BS8112A", 0, },
{ }
};
MODULE_DEVICE_TABLE(i2c, BS8112A_idtable);
static struct i2c_driver BS8112A_driver = {
.driver = {
.name = "BS8112A",
.of_match_table = of_match_ptr(BS8112A_of_match),
},
.id_table = BS8112A_idtable,
.probe = BS8112A_probe,
.remove = BS8112A_remove,
};
module_i2c_driver(BS8112A_driver);
MODULE_AUTHOR("lutao <[email protected]>");
MODULE_DESCRIPTION("Driver for BS8112A Touch Sensor");
MODULE_LICENSE("GPL");
二、修改Makefile和Kconfig,并在make menuconfig后选中该新增驱动。
三、设备树文件修改
&i2c0 {
pinctrl-0 = <&i2c0_pins>;
pinctrl-names = "default";
status = "okay";
clock-frequency = <100000>;
ssd1306fb@48 {
compatible = "solomon,ssd1306fb-i2c";
reg = <0x48>;
};
bs8112a@28 {
compatible = "BS8112A";
reg = <0x50>;
};
};
注意我这里是I2C0上面挂了2个子设备,BS8112A的设备地址设置为0x50,即0xA0右移一位。
四、编译测试。
内核启动打印信息可以显示如下:
[ 0.941804] input: BS8112A Touch Sense Keyboard as /devices/virtual/input/input0
打开/dev/input/event0即可进行应用层开发。