mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 15:41:36 +00:00
ed5c2f5fd1
The value returned by an i2c driver's remove function is mostly ignored. (Only an error message is printed if the value is non-zero that the error is ignored.) So change the prototype of the remove function to return no value. This way driver authors are not tempted to assume that passing an error to the upper layer is a good idea. All drivers are adapted accordingly. There is no intended change of behaviour, all callbacks were prepared to return 0 before. Reviewed-by: Peter Senna Tschudin <peter.senna@gmail.com> Reviewed-by: Jeremy Kerr <jk@codeconstruct.com.au> Reviewed-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com> Reviewed-by: Javier Martinez Canillas <javierm@redhat.com> Reviewed-by: Crt Mori <cmo@melexis.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Acked-by: Marek Behún <kabel@kernel.org> # for leds-turris-omnia Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Reviewed-by: Petr Machata <petrm@nvidia.com> # for mlxsw Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com> # for surface3_power Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> # for bmc150-accel-i2c + kxcjk-1013 Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> # for media/* + staging/media/* Acked-by: Miguel Ojeda <ojeda@kernel.org> # for auxdisplay/ht16k33 + auxdisplay/lcd2s Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> # for versaclock5 Reviewed-by: Ajay Gupta <ajayg@nvidia.com> # for ucsi_ccg Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> # for iio Acked-by: Peter Rosin <peda@axentia.se> # for i2c-mux-*, max9860 Acked-by: Adrien Grassein <adrien.grassein@gmail.com> # for lontium-lt8912b Reviewed-by: Jean Delvare <jdelvare@suse.de> # for hwmon, i2c-core and i2c/muxes Acked-by: Corey Minyard <cminyard@mvista.com> # for IPMI Reviewed-by: Vladimir Oltean <olteanv@gmail.com> Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> # for drivers/power Acked-by: Krzysztof Hałasa <khalasa@piap.pl> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Wolfram Sang <wsa@kernel.org>
231 lines
5.2 KiB
C
231 lines
5.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* LM8333 keypad driver
|
|
* Copyright (C) 2012 Wolfram Sang, Pengutronix <kernel@pengutronix.de>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/input/matrix_keypad.h>
|
|
#include <linux/input/lm8333.h>
|
|
|
|
#define LM8333_FIFO_READ 0x20
|
|
#define LM8333_DEBOUNCE 0x22
|
|
#define LM8333_READ_INT 0xD0
|
|
#define LM8333_ACTIVE 0xE4
|
|
#define LM8333_READ_ERROR 0xF0
|
|
|
|
#define LM8333_KEYPAD_IRQ (1 << 0)
|
|
#define LM8333_ERROR_IRQ (1 << 3)
|
|
|
|
#define LM8333_ERROR_KEYOVR 0x04
|
|
#define LM8333_ERROR_FIFOOVR 0x40
|
|
|
|
#define LM8333_FIFO_TRANSFER_SIZE 16
|
|
|
|
#define LM8333_NUM_ROWS 8
|
|
#define LM8333_NUM_COLS 16
|
|
#define LM8333_ROW_SHIFT 4
|
|
|
|
struct lm8333 {
|
|
struct i2c_client *client;
|
|
struct input_dev *input;
|
|
unsigned short keycodes[LM8333_NUM_ROWS << LM8333_ROW_SHIFT];
|
|
};
|
|
|
|
/* The accessors try twice because the first access may be needed for wakeup */
|
|
#define LM8333_READ_RETRIES 2
|
|
|
|
int lm8333_read8(struct lm8333 *lm8333, u8 cmd)
|
|
{
|
|
int retries = 0, ret;
|
|
|
|
do {
|
|
ret = i2c_smbus_read_byte_data(lm8333->client, cmd);
|
|
} while (ret < 0 && retries++ < LM8333_READ_RETRIES);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int lm8333_write8(struct lm8333 *lm8333, u8 cmd, u8 val)
|
|
{
|
|
int retries = 0, ret;
|
|
|
|
do {
|
|
ret = i2c_smbus_write_byte_data(lm8333->client, cmd, val);
|
|
} while (ret < 0 && retries++ < LM8333_READ_RETRIES);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int lm8333_read_block(struct lm8333 *lm8333, u8 cmd, u8 len, u8 *buf)
|
|
{
|
|
int retries = 0, ret;
|
|
|
|
do {
|
|
ret = i2c_smbus_read_i2c_block_data(lm8333->client,
|
|
cmd, len, buf);
|
|
} while (ret < 0 && retries++ < LM8333_READ_RETRIES);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void lm8333_key_handler(struct lm8333 *lm8333)
|
|
{
|
|
struct input_dev *input = lm8333->input;
|
|
u8 keys[LM8333_FIFO_TRANSFER_SIZE];
|
|
u8 code, pressed;
|
|
int i, ret;
|
|
|
|
ret = lm8333_read_block(lm8333, LM8333_FIFO_READ,
|
|
LM8333_FIFO_TRANSFER_SIZE, keys);
|
|
if (ret != LM8333_FIFO_TRANSFER_SIZE) {
|
|
dev_err(&lm8333->client->dev,
|
|
"Error %d while reading FIFO\n", ret);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < LM8333_FIFO_TRANSFER_SIZE && keys[i]; i++) {
|
|
pressed = keys[i] & 0x80;
|
|
code = keys[i] & 0x7f;
|
|
|
|
input_event(input, EV_MSC, MSC_SCAN, code);
|
|
input_report_key(input, lm8333->keycodes[code], pressed);
|
|
}
|
|
|
|
input_sync(input);
|
|
}
|
|
|
|
static irqreturn_t lm8333_irq_thread(int irq, void *data)
|
|
{
|
|
struct lm8333 *lm8333 = data;
|
|
u8 status = lm8333_read8(lm8333, LM8333_READ_INT);
|
|
|
|
if (!status)
|
|
return IRQ_NONE;
|
|
|
|
if (status & LM8333_ERROR_IRQ) {
|
|
u8 err = lm8333_read8(lm8333, LM8333_READ_ERROR);
|
|
|
|
if (err & (LM8333_ERROR_KEYOVR | LM8333_ERROR_FIFOOVR)) {
|
|
u8 dummy[LM8333_FIFO_TRANSFER_SIZE];
|
|
|
|
lm8333_read_block(lm8333, LM8333_FIFO_READ,
|
|
LM8333_FIFO_TRANSFER_SIZE, dummy);
|
|
}
|
|
dev_err(&lm8333->client->dev, "Got error %02x\n", err);
|
|
}
|
|
|
|
if (status & LM8333_KEYPAD_IRQ)
|
|
lm8333_key_handler(lm8333);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int lm8333_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
const struct lm8333_platform_data *pdata =
|
|
dev_get_platdata(&client->dev);
|
|
struct lm8333 *lm8333;
|
|
struct input_dev *input;
|
|
int err, active_time;
|
|
|
|
if (!pdata)
|
|
return -EINVAL;
|
|
|
|
active_time = pdata->active_time ?: 500;
|
|
if (active_time / 3 <= pdata->debounce_time / 3) {
|
|
dev_err(&client->dev, "Active time not big enough!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
lm8333 = kzalloc(sizeof(*lm8333), GFP_KERNEL);
|
|
input = input_allocate_device();
|
|
if (!lm8333 || !input) {
|
|
err = -ENOMEM;
|
|
goto free_mem;
|
|
}
|
|
|
|
lm8333->client = client;
|
|
lm8333->input = input;
|
|
|
|
input->name = client->name;
|
|
input->dev.parent = &client->dev;
|
|
input->id.bustype = BUS_I2C;
|
|
|
|
input_set_capability(input, EV_MSC, MSC_SCAN);
|
|
|
|
err = matrix_keypad_build_keymap(pdata->matrix_data, NULL,
|
|
LM8333_NUM_ROWS, LM8333_NUM_COLS,
|
|
lm8333->keycodes, input);
|
|
if (err)
|
|
goto free_mem;
|
|
|
|
if (pdata->debounce_time) {
|
|
err = lm8333_write8(lm8333, LM8333_DEBOUNCE,
|
|
pdata->debounce_time / 3);
|
|
if (err)
|
|
dev_warn(&client->dev, "Unable to set debounce time\n");
|
|
}
|
|
|
|
if (pdata->active_time) {
|
|
err = lm8333_write8(lm8333, LM8333_ACTIVE,
|
|
pdata->active_time / 3);
|
|
if (err)
|
|
dev_warn(&client->dev, "Unable to set active time\n");
|
|
}
|
|
|
|
err = request_threaded_irq(client->irq, NULL, lm8333_irq_thread,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"lm8333", lm8333);
|
|
if (err)
|
|
goto free_mem;
|
|
|
|
err = input_register_device(input);
|
|
if (err)
|
|
goto free_irq;
|
|
|
|
i2c_set_clientdata(client, lm8333);
|
|
return 0;
|
|
|
|
free_irq:
|
|
free_irq(client->irq, lm8333);
|
|
free_mem:
|
|
input_free_device(input);
|
|
kfree(lm8333);
|
|
return err;
|
|
}
|
|
|
|
static void lm8333_remove(struct i2c_client *client)
|
|
{
|
|
struct lm8333 *lm8333 = i2c_get_clientdata(client);
|
|
|
|
free_irq(client->irq, lm8333);
|
|
input_unregister_device(lm8333->input);
|
|
kfree(lm8333);
|
|
}
|
|
|
|
static const struct i2c_device_id lm8333_id[] = {
|
|
{ "lm8333", 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, lm8333_id);
|
|
|
|
static struct i2c_driver lm8333_driver = {
|
|
.driver = {
|
|
.name = "lm8333",
|
|
},
|
|
.probe = lm8333_probe,
|
|
.remove = lm8333_remove,
|
|
.id_table = lm8333_id,
|
|
};
|
|
module_i2c_driver(lm8333_driver);
|
|
|
|
MODULE_AUTHOR("Wolfram Sang <kernel@pengutronix.de>");
|
|
MODULE_DESCRIPTION("LM8333 keyboard driver");
|
|
MODULE_LICENSE("GPL v2");
|