licheepi nano BS8112A触摸按键

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即可进行应用层开发。

你可能感兴趣的:(linux驱动)