From patchwork Fri Oct 9 10:19:36 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "qipeng.zha" X-Patchwork-Id: 7358671 Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 13B54BEEA4 for ; Fri, 9 Oct 2015 02:15:37 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 0300F2081E for ; Fri, 9 Oct 2015 02:15:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id ADA8B20819 for ; Fri, 9 Oct 2015 02:15:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756442AbbJICPd (ORCPT ); Thu, 8 Oct 2015 22:15:33 -0400 Received: from mga14.intel.com ([192.55.52.115]:18343 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756435AbbJICPd (ORCPT ); Thu, 8 Oct 2015 22:15:33 -0400 Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by fmsmga103.fm.intel.com with ESMTP; 08 Oct 2015 19:15:32 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.17,657,1437462000"; d="scan'208";a="822795611" Received: from shbuild999.sh.intel.com ([10.239.146.206]) by fmsmga002.fm.intel.com with ESMTP; 08 Oct 2015 19:15:32 -0700 From: Qipeng Zha To: linux-input@vger.kernel.org Cc: dmitry.torokhov@gmail.com Subject: [PATCH] input:keyboard: add Cypress button driver for Intel Platform Date: Fri, 9 Oct 2015 18:19:36 +0800 Message-Id: <1444385976-57339-1-git-send-email-qipeng.zha@intel.com> X-Mailer: git-send-email 1.8.3.2 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-5.0 required=5.0 tests=BAYES_00, DATE_IN_FUTURE_06_12, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This driver is to support Cypress CY8CMBR3XXX family controller, which is used as button input for Intel platforms. Signed-off-by: Qipeng Zha --- drivers/input/keyboard/Kconfig | 7 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/cypress-keyboard.c | 278 ++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 drivers/input/keyboard/cypress-keyboard.c diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index a89ba7c..f39f148 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -686,4 +686,11 @@ config KEYBOARD_CAP11XX To compile this driver as a module, choose M here: the module will be called cap11xx. +config KEYBOARD_CYPRESS_BUTTON + tristate "Cypress Button" + depends on I2C + help + Say Y here to enable support for Cypress button for + Intel platforms. + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 4707678..5a73138 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYBOARD_CYPRESS_BUTTON) += cypress-keyboard.o diff --git a/drivers/input/keyboard/cypress-keyboard.c b/drivers/input/keyboard/cypress-keyboard.c new file mode 100644 index 0000000..4a2e7f7 --- /dev/null +++ b/drivers/input/keyboard/cypress-keyboard.c @@ -0,0 +1,278 @@ +/* + * Cypress button driver + * + * Copyright (c) 2015, Intel Corporation. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* registers */ +#define SENSOR_EN 0x0 +#define SENSOR_CS0 0 +#define SENSOR_CS1 1 +#define BUTTON_STAT 0xAA +#define BUTTON_ACTIVE 1 + +/* retry I2C transfer in case of autosleep */ +#define I2C_RETRY_TIMES 10 + +struct cy_kb { + struct i2c_client *client; + struct input_dev *input_dev; + struct mutex mutex; + unsigned int irq; + int gpio; + u16 button_status; + bool suspended; +}; + +static int cy_kb_read(struct cy_kb *data, + u16 reg, u16 len, void *buf) +{ + int ret; + u8 regbuf[1]; + struct i2c_msg xfer[2]; + int retry = I2C_RETRY_TIMES; + + regbuf[0] = reg & 0xff; + + xfer[0].addr = data->client->addr; + xfer[0].flags = 0; + xfer[0].len = sizeof(regbuf); + xfer[0].buf = regbuf; + + xfer[1].addr = data->client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = len; + xfer[1].buf = buf; + +retry_read: + ret = i2c_transfer(data->client->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret != ARRAY_SIZE(xfer)) { + if (retry--) { + dev_dbg(&data->client->dev, "i2c read retry\n"); + goto retry_read; + } else { + dev_err(&data->client->dev, "i2c transfer failed (%d)\n", + ret); + return -EIO; + } + } + + return 0; +} + +static int cy_kb_write(struct cy_kb *data, u8 reg, u16 val) +{ + int ret; + u8 *buf; + int count; + int retry = I2C_RETRY_TIMES; + + count = sizeof(val) + 1; + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = reg & 0xff; + memcpy(&buf[1], &val, sizeof(val)); + +retry_write: + ret = i2c_master_send(data->client, buf, count); + if (ret != count) { + if (retry--) { + dev_dbg(&data->client->dev, "i2c write retry\n"); + goto retry_write; + } else { + dev_err(&data->client->dev, "i2c send failed (%d)\n", + ret); + ret = -EIO; + } + } else { + ret = 0; + } + + kfree(buf); + return ret; +} + +static irqreturn_t cy_kb_interrupt(int irq, void *dev_id) +{ + int ret; + u8 buf = 0x0; + struct cy_kb *data = dev_id; + + mutex_lock(&data->mutex); + + ret = cy_kb_read(data, BUTTON_STAT, sizeof(buf), &buf); + if (ret) { + dev_err(&data->client->dev, "can't read button status\n"); + goto out; + } + dev_dbg(&data->client->dev, "Button status 0x%02x\n", buf); + + if ((buf ^ data->button_status) & (BUTTON_ACTIVE << SENSOR_CS0)) { + input_event(data->input_dev, EV_KEY, KEY_BACK, + (buf >> SENSOR_CS0) & BUTTON_ACTIVE); + } + + if ((buf ^ data->button_status) & (BUTTON_ACTIVE << SENSOR_CS1)) { + input_event(data->input_dev, EV_KEY, KEY_MENU, + (buf >> SENSOR_CS1) & BUTTON_ACTIVE); + } + + input_sync(data->input_dev); + data->button_status = buf; +out: + mutex_unlock(&data->mutex); + return IRQ_HANDLED; +} + +static int cy_kb_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct cy_kb *data; + struct gpio_desc *gpio; + + data = devm_kzalloc(&client->dev, sizeof(struct cy_kb), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->mutex); + data->client = client; + data->irq = client->irq; + gpio = devm_gpiod_get_index(&client->dev, "cypress_gpio_int", 0); + if (!IS_ERR(gpio)) { + data->gpio = desc_to_gpio(gpio); + data->irq = gpiod_to_irq(gpio_to_desc(data->gpio)); + } else + dev_err(&client->dev, "Failed to get gpio interrupt\n"); + + ret = request_threaded_irq(data->irq, NULL, cy_kb_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + client->name, data); + if (ret) { + dev_err(&client->dev, "Failed to register irq\n"); + return ret; + } + + data->input_dev = input_allocate_device(); + if (!data->input_dev) { + dev_err(&client->dev, "Failed to allocate input device\n"); + ret = -ENOMEM; + goto err_alloc_input; + } + data->input_dev->name = client->name; + data->input_dev->id.bustype = BUS_I2C; + data->input_dev->dev.parent = &data->client->dev; + input_set_capability(data->input_dev, EV_KEY, KEY_BACK); + input_set_capability(data->input_dev, EV_KEY, KEY_MENU); + ret = input_register_device(data->input_dev); + if (ret) { + dev_err(&client->dev, "Failed to register input device\n"); + goto err_reg_input; + } + + return 0; + +err_reg_input: + input_free_device(data->input_dev); +err_alloc_input: + free_irq(data->irq, data); + gpio_free(data->gpio); + + return ret; +} + +static int cy_kb_remove(struct i2c_client *client) +{ + struct cy_kb *data = i2c_get_clientdata(client); + + free_irq(data->irq, data); + gpio_free(data->gpio); + input_unregister_device(data->input_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cy_kb_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cy_kb *data = i2c_get_clientdata(client); + + mutex_lock(&data->mutex); + if (!data->suspended) + cy_kb_write(data, SENSOR_EN, 0); + data->suspended = true; + mutex_unlock(&data->mutex); + + return 0; +} + +static int cy_kb_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cy_kb *data = i2c_get_clientdata(client); + + mutex_lock(&data->mutex); + if (data->suspended) { + u16 val = (1 << SENSOR_CS0) | (1 << SENSOR_CS1); + + cy_kb_write(data, SENSOR_EN, val); + } + data->suspended = false; + mutex_unlock(&data->mutex); + + return 0; +} +static SIMPLE_DEV_PM_OPS(cy_kb_pm_ops, cy_kb_suspend, cy_kb_resume); +#endif + +static const struct i2c_device_id cy_kb_id[] = { + { "CY8M3108:00", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cy_kb_id); + +static struct acpi_device_id cy_kb_acpi_id[] = { + { "CY8M3108", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, cy_kb_acpi_id); + +static struct i2c_driver cy_kb_driver = { + .driver = { + .name = "cypressbutton", + .acpi_match_table = ACPI_PTR(cy_kb_acpi_id), +#ifdef CONFIG_PM_SLEEP + .pm = &cy_kb_pm_ops, +#endif + }, + .probe = cy_kb_probe, + .remove = cy_kb_remove, + .id_table = cy_kb_id, +}; + +module_i2c_driver(cy_kb_driver); + +MODULE_AUTHOR("Qipeng Zha"); +MODULE_DESCRIPTION("Cypress CY8CMBRXXX driver"); +MODULE_LICENSE("GPL v2");