aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ubicom32/files/arch/ubicom32/mach-common/ubicom32hid.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/ubicom32/files/arch/ubicom32/mach-common/ubicom32hid.c')
-rw-r--r--target/linux/ubicom32/files/arch/ubicom32/mach-common/ubicom32hid.c557
1 files changed, 557 insertions, 0 deletions
diff --git a/target/linux/ubicom32/files/arch/ubicom32/mach-common/ubicom32hid.c b/target/linux/ubicom32/files/arch/ubicom32/mach-common/ubicom32hid.c
new file mode 100644
index 000000000..3318eff88
--- /dev/null
+++ b/target/linux/ubicom32/files/arch/ubicom32/mach-common/ubicom32hid.c
@@ -0,0 +1,557 @@
+/*
+ * arch/ubicom32/mach-common/ubicom32hid.c
+ * I2C driver for HID coprocessor found on some DPF implementations.
+ *
+ * (C) Copyright 2009, Ubicom, Inc.
+ *
+ * This file is part of the Ubicom32 Linux Kernel Port.
+ *
+ * The Ubicom32 Linux Kernel Port is free software: you can redistribute
+ * it and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Ubicom32 Linux Kernel Port is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with the Ubicom32 Linux Kernel Port. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Ubicom32 implementation derived from (with many thanks):
+ * arch/m68knommu
+ * arch/blackfin
+ * arch/parisc
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/input.h>
+#include <linux/input-polldev.h>
+
+#include <asm/ubicom32hid.h>
+
+#define DRIVER_NAME "ubicom32hid"
+
+#ifdef DEBUG
+static int ubicom32hid_debug;
+#endif
+
+static const struct i2c_device_id ubicom32hid_id[] = {
+ { DRIVER_NAME, },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ubicom32hid_id);
+
+/*
+ * Define this to make IR checking strict, in general, it's not needed
+ */
+#undef UBICOM32HID_STRICT_IR_CHECK
+
+#define UBICOM32HID_CMD_SET_PWM 0x01
+#define UBICOM32HID_CMD_SET_BL_EN 0x02
+#define UBICOM32HID_BL_EN_LOW 0x00
+#define UBICOM32HID_BL_EN_HIZ 0x01
+#define UBICOM32HID_BL_EN_HI 0x02
+#define UBICOM32HID_CMD_FLUSH 0x99
+#define UBICOM32HID_CMD_RESET 0x99
+#define UBICOM32HID_CMD_GET_IR_SWITCH 0xC0
+#define UBICOM32HID_CMD_GET_REVISION 0xfd
+#define UBICOM32HID_CMD_GET_DEVICE_ID 0xfe
+#define UBICOM32HID_CMD_GET_VERSION 0xff
+#define UBICOM32HID_DEVICE_ID 0x49
+
+#define UBICOM32HID_MAX_BRIGHTNESS_PWM 255
+
+/*
+ * Data structure returned by the HID device
+ */
+struct ubicom32hid_input_data {
+ uint32_t ircmd;
+ uint8_t sw_state;
+ uint8_t sw_changed;
+};
+
+/*
+ * Our private data
+ */
+struct ubicom32hid_data {
+ /*
+ * Pointer to the platform data structure, we need the settings.
+ */
+ const struct ubicom32hid_platform_data *pdata;
+
+ /*
+ * Backlight device
+ */
+ struct backlight_device *bldev;
+
+ /*
+ * I2C client, for sending messages to the HID device
+ */
+ struct i2c_client *client;
+
+ /*
+ * Current intensity, used for get_intensity.
+ */
+ int cur_intensity;
+
+ /*
+ * Input subsystem
+ * We won't register an input subsystem if there are no mappings.
+ */
+ struct input_polled_dev *poll_dev;
+};
+
+
+/*
+ * ubicom32hid_set_intensity
+ */
+static int ubicom32hid_set_intensity(struct backlight_device *bd)
+{
+ struct ubicom32hid_data *ud =
+ (struct ubicom32hid_data *)bl_get_data(bd);
+ int intensity = bd->props.brightness;
+ int reg;
+ u8_t val;
+ int ret;
+
+ /*
+ * If we're blanked the the intensity doesn't matter.
+ */
+ if ((bd->props.power != FB_BLANK_UNBLANK) ||
+ (bd->props.fb_blank != FB_BLANK_UNBLANK)) {
+ intensity = 0;
+ }
+
+ /*
+ * Set the brightness based on the type of backlight
+ */
+ if (ud->pdata->type == UBICOM32HID_BL_TYPE_BINARY) {
+ reg = UBICOM32HID_CMD_SET_BL_EN;
+ if (intensity) {
+ val = ud->pdata->invert
+ ? UBICOM32HID_BL_EN_LOW : UBICOM32HID_BL_EN_HI;
+ } else {
+ val = ud->pdata->invert
+ ? UBICOM32HID_BL_EN_HI : UBICOM32HID_BL_EN_LOW;
+ }
+ } else {
+ reg = UBICOM32HID_CMD_SET_PWM;
+ val = ud->pdata->invert
+ ? (UBICOM32HID_MAX_BRIGHTNESS_PWM - intensity) :
+ intensity;
+ }
+
+ /*
+ * Send the command
+ */
+ ret = i2c_smbus_write_byte_data(ud->client, reg, val);
+ if (ret < 0) {
+ dev_warn(&ud->client->dev, "Unable to write backlight err=%d\n",
+ ret);
+ return ret;
+ }
+
+ ud->cur_intensity = intensity;
+
+ return 0;
+}
+
+/*
+ * ubicom32hid_get_intensity
+ * Return the current intensity of the backlight.
+ */
+static int ubicom32hid_get_intensity(struct backlight_device *bd)
+{
+ struct ubicom32hid_data *ud =
+ (struct ubicom32hid_data *)bl_get_data(bd);
+
+ return ud->cur_intensity;
+}
+
+/*
+ * ubicom32hid_verify_data
+ * Verify the data to see if there is any action to be taken
+ *
+ * Returns 0 if no action is to be taken, non-zero otherwise
+ */
+static int ubicom32hid_verify_data(struct ubicom32hid_data *ud,
+ struct ubicom32hid_input_data *data)
+{
+ uint8_t *ircmd = (uint8_t *)&(data->ircmd);
+
+ /*
+ * ircmd == DEADBEEF means ir queue is empty. Since this is a
+ * meaningful code, that means the rest of the message is most likely
+ * correct, so only process the data if the switch state has changed.
+ */
+ if (data->ircmd == 0xDEADBEEF) {
+ return data->sw_changed != 0;
+ }
+
+ /*
+ * We have an ircmd which is not empty:
+ * Data[1] should be the complement of Data[0]
+ */
+ if (ircmd[0] != (u8_t)~ircmd[1]) {
+ return 0;
+ }
+
+#ifdef UBICOM32HID_STRICT_IR_CHECK
+ /*
+ * It seems that some remote controls don't follow the NEC protocol
+ * properly, so only do this check if the remote does indeed follow the
+ * spec. Data[3] should be the complement of Data[2]
+ */
+ if (ircmd[2] == (u8_t)~ircmd[3]) {
+ return 1;
+ }
+
+ /*
+ * For non-compliant remotes, check the system code according to what
+ * they send.
+ */
+ if ((ircmd[2] != UBICOM32HID_IR_SYSTEM_CODE_CHECK) ||
+ (ircmd[3] != UBICOM32HID_IR_SYSTEM_CODE)) {
+ return 0;
+ }
+#endif
+
+ /*
+ * Data checks out, process
+ */
+ return 1;
+}
+
+/*
+ * ubicom32hid_poll_input
+ * Poll the input from the HID device.
+ */
+static void ubicom32hid_poll_input(struct input_polled_dev *dev)
+{
+ struct ubicom32hid_data *ud = (struct ubicom32hid_data *)dev->private;
+ const struct ubicom32hid_platform_data *pdata = ud->pdata;
+ struct ubicom32hid_input_data data;
+ struct input_dev *id = dev->input;
+ int i;
+ int sync_needed = 0;
+ uint8_t cmd;
+ int ret;
+
+ /*
+ * Flush the queue
+ */
+ cmd = UBICOM32HID_CMD_FLUSH;
+ ret = i2c_master_send(ud->client, &cmd, 1);
+ if (ret < 0) {
+ return;
+ }
+
+ ret = i2c_smbus_read_i2c_block_data(
+ ud->client, UBICOM32HID_CMD_GET_IR_SWITCH, 6, (void *)&data);
+ if (ret < 0) {
+ return;
+ }
+
+ /*
+ * Verify the data to see if there is any action to be taken
+ */
+ if (!ubicom32hid_verify_data(ud, &data)) {
+ return;
+ }
+
+#ifdef DEBUG
+ if (ubicom32hid_debug) {
+ printk("Polled ircmd=%8x swstate=%2x swchanged=%2x\n",
+ data.ircmd, data.sw_state, data.sw_changed);
+ }
+#endif
+
+ /*
+ * Process changed switches
+ */
+ if (data.sw_changed) {
+ const struct ubicom32hid_button *ub = pdata->buttons;
+ for (i = 0; i < pdata->nbuttons; i++, ub++) {
+ uint8_t mask = (1 << ub->bit);
+ if (!(data.sw_changed & mask)) {
+ continue;
+ }
+
+ sync_needed = 1;
+ input_event(id, ub->type, ub->code,
+ (data.sw_state & mask) ? 1 : 0);
+ }
+ }
+ if (sync_needed) {
+ input_sync(id);
+ }
+
+ /*
+ * Process ir codes
+ */
+ if (data.ircmd != 0xDEADBEEF) {
+ const struct ubicom32hid_ir *ui = pdata->ircodes;
+ for (i = 0; i < pdata->nircodes; i++, ui++) {
+ if (ui->ir_code == data.ircmd) {
+ /*
+ * Simulate a up/down event
+ */
+ input_event(id, ui->type, ui->code, 1);
+ input_sync(id);
+ input_event(id, ui->type, ui->code, 0);
+ input_sync(id);
+ }
+ }
+ }
+}
+
+
+/*
+ * Backlight ops
+ */
+static struct backlight_ops ubicom32hid_blops = {
+ .get_brightness = ubicom32hid_get_intensity,
+ .update_status = ubicom32hid_set_intensity,
+};
+
+/*
+ * ubicom32hid_probe
+ */
+static int ubicom32hid_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ubicom32hid_platform_data *pdata;
+ struct ubicom32hid_data *ud;
+ int ret;
+ int i;
+ u8 version[2];
+ char buf[1];
+
+ pdata = client->dev.platform_data;
+ if (pdata == NULL) {
+ return -ENODEV;
+ }
+
+ /*
+ * See if we even have a device available before allocating memory.
+ *
+ * Hard reset the device
+ */
+ ret = gpio_request(pdata->gpio_reset, "ubicom32hid-reset");
+ if (ret < 0) {
+ return ret;
+ }
+ gpio_direction_output(pdata->gpio_reset, pdata->gpio_reset_polarity);
+ udelay(100);
+ gpio_set_value(pdata->gpio_reset, !pdata->gpio_reset_polarity);
+ udelay(100);
+
+ /*
+ * soft reset the device. It sometimes takes a while to do this.
+ */
+ for (i = 0; i < 50; i++) {
+ buf[0] = UBICOM32HID_CMD_RESET;
+ ret = i2c_master_send(client, buf, 1);
+ if (ret > 0) {
+ break;
+ }
+ udelay(10000);
+ }
+ if (i == 50) {
+ dev_warn(&client->dev, "Unable to reset device\n");
+ goto fail;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, UBICOM32HID_CMD_GET_DEVICE_ID);
+ if (ret != UBICOM32HID_DEVICE_ID) {
+ dev_warn(&client->dev, "Incorrect device id %02x\n", buf[0]);
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ ret = i2c_smbus_read_byte_data(client, UBICOM32HID_CMD_GET_VERSION);
+ if (ret < 0) {
+ dev_warn(&client->dev, "Unable to get version\n");
+ goto fail;
+ }
+ version[0] = ret;
+
+ ret = i2c_smbus_read_byte_data(client, UBICOM32HID_CMD_GET_REVISION);
+ if (ret < 0) {
+ dev_warn(&client->dev, "Unable to get revision\n");
+ goto fail;
+ }
+ version[1] = ret;
+
+ /*
+ * Allocate our private data
+ */
+ ud = kzalloc(sizeof(struct ubicom32hid_data), GFP_KERNEL);
+ if (!ud) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ ud->pdata = pdata;
+ ud->client = client;
+
+ /*
+ * Register our backlight device
+ */
+ ud->bldev = backlight_device_register(DRIVER_NAME, &client->dev,
+ ud, &ubicom32hid_blops);
+ if (IS_ERR(ud->bldev)) {
+ ret = PTR_ERR(ud->bldev);
+ goto fail2;
+ }
+ platform_set_drvdata(client, ud);
+
+ /*
+ * Start up the backlight with the requested intensity
+ */
+ ud->bldev->props.power = FB_BLANK_UNBLANK;
+ ud->bldev->props.max_brightness =
+ (pdata->type == UBICOM32HID_BL_TYPE_PWM) ?
+ UBICOM32HID_MAX_BRIGHTNESS_PWM : 1;
+ if (pdata->default_intensity < ud->bldev->props.max_brightness) {
+ ud->bldev->props.brightness = pdata->default_intensity;
+ } else {
+ dev_warn(&client->dev, "Default brightness out of range, "
+ "setting to max\n");
+ ud->bldev->props.brightness = ud->bldev->props.max_brightness;
+ }
+
+ ubicom32hid_set_intensity(ud->bldev);
+
+ /*
+ * Check to see if we have any inputs
+ */
+ if (!pdata->nbuttons && !pdata->nircodes) {
+ goto done;
+ }
+
+ /*
+ * We have buttons or codes, we must register an input device
+ */
+ ud->poll_dev = input_allocate_polled_device();
+ if (!ud->poll_dev) {
+ ret = -ENOMEM;
+ goto fail3;
+ }
+
+ /*
+ * Setup the polling to default to 100ms
+ */
+ ud->poll_dev->poll = ubicom32hid_poll_input;
+ ud->poll_dev->poll_interval =
+ pdata->poll_interval ? pdata->poll_interval : 100;
+ ud->poll_dev->private = ud;
+
+ ud->poll_dev->input->name =
+ pdata->input_name ? pdata->input_name : "Ubicom32HID";
+ ud->poll_dev->input->phys = "ubicom32hid/input0";
+ ud->poll_dev->input->dev.parent = &client->dev;
+ ud->poll_dev->input->id.bustype = BUS_I2C;
+
+ /*
+ * Set the capabilities by running through the buttons and ir codes
+ */
+ for (i = 0; i < pdata->nbuttons; i++) {
+ const struct ubicom32hid_button *ub = &pdata->buttons[i];
+
+ input_set_capability(ud->poll_dev->input,
+ ub->type ? ub->type : EV_KEY, ub->code);
+ }
+
+ for (i = 0; i < pdata->nircodes; i++) {
+ const struct ubicom32hid_ir *ui = &pdata->ircodes[i];
+
+ input_set_capability(ud->poll_dev->input,
+ ui->type ? ui->type : EV_KEY, ui->code);
+ }
+
+ ret = input_register_polled_device(ud->poll_dev);
+ if (ret) {
+ goto fail3;
+ }
+
+done:
+ printk(KERN_INFO DRIVER_NAME ": enabled, version=%02x.%02x\n",
+ version[0], version[1]);
+
+ return 0;
+
+fail3:
+ gpio_free(ud->pdata->gpio_reset);
+ backlight_device_unregister(ud->bldev);
+fail2:
+ kfree(ud);
+fail:
+ gpio_free(pdata->gpio_reset);
+ return ret;
+}
+
+/*
+ * ubicom32hid_remove
+ */
+static int ubicom32hid_remove(struct i2c_client *client)
+{
+ struct ubicom32hid_data *ud =
+ (struct ubicom32hid_data *)platform_get_drvdata(client);
+
+ gpio_free(ud->pdata->gpio_reset);
+
+ backlight_device_unregister(ud->bldev);
+
+ if (ud->poll_dev) {
+ input_unregister_polled_device(ud->poll_dev);
+ input_free_polled_device(ud->poll_dev);
+ }
+
+ platform_set_drvdata(client, NULL);
+
+ kfree(ud);
+
+ return 0;
+}
+
+static struct i2c_driver ubicom32hid_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ubicom32hid_probe,
+ .remove = __exit_p(ubicom32hid_remove),
+ .id_table = ubicom32hid_id,
+};
+
+/*
+ * ubicom32hid_init
+ */
+static int __init ubicom32hid_init(void)
+{
+ return i2c_add_driver(&ubicom32hid_driver);
+}
+module_init(ubicom32hid_init);
+
+/*
+ * ubicom32hid_exit
+ */
+static void __exit ubicom32hid_exit(void)
+{
+ i2c_del_driver(&ubicom32hid_driver);
+}
+module_exit(ubicom32hid_exit);
+
+MODULE_AUTHOR("Pat Tjin <@ubicom.com>")
+MODULE_DESCRIPTION("Ubicom HID driver");
+MODULE_LICENSE("GPL");