From patchwork Thu Aug 21 18:19:03 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Murphy X-Patchwork-Id: 4759401 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.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 34A15C0338 for ; Thu, 21 Aug 2014 18:20:13 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id BF9EF2013A for ; Thu, 21 Aug 2014 18:20:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 392D3200ED for ; Thu, 21 Aug 2014 18:20:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753744AbaHUSTt (ORCPT ); Thu, 21 Aug 2014 14:19:49 -0400 Received: from devils.ext.ti.com ([198.47.26.153]:42586 "EHLO devils.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753498AbaHUSTb (ORCPT ); Thu, 21 Aug 2014 14:19:31 -0400 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by devils.ext.ti.com (8.13.7/8.13.7) with ESMTP id s7LIJ7uc025366; Thu, 21 Aug 2014 13:19:07 -0500 Received: from DLEE70.ent.ti.com (dlemailx.itg.ti.com [157.170.170.113]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id s7LIJ7GK014058; Thu, 21 Aug 2014 13:19:07 -0500 Received: from dlep33.itg.ti.com (157.170.170.75) by DLEE70.ent.ti.com (157.170.170.113) with Microsoft SMTP Server id 14.3.174.1; Thu, 21 Aug 2014 13:19:07 -0500 Received: from legion.dal.design.ti.com (legion.dal.design.ti.com [128.247.22.53]) by dlep33.itg.ti.com (8.14.3/8.13.8) with ESMTP id s7LIJ76q022876; Thu, 21 Aug 2014 13:19:07 -0500 Received: from localhost (j-172-22-83-125.vpn.ti.com [172.22.83.125]) by legion.dal.design.ti.com (8.11.7p1+Sun/8.11.7) with ESMTP id s7LIJ6t08798; Thu, 21 Aug 2014 13:19:06 -0500 (CDT) From: Dan Murphy To: , CC: , , , Dan Murphy Subject: [PATCH 2/2] input: misc: Add support for the DRV2667 haptic driver Date: Thu, 21 Aug 2014 13:19:03 -0500 Message-ID: <1408645143-25690-3-git-send-email-dmurphy@ti.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1408645143-25690-1-git-send-email-dmurphy@ti.com> References: <1408645143-25690-1-git-send-email-dmurphy@ti.com> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-7.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, 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 Adding support for the DRV2667 haptic driver. This device has the ability to store vibration patterns in RAM and execute them once the GO bit is set. The initial driver sets a basic waveform in the first waveform sequence and will play the waveform when the GO bit is set and will continously play the waveform until the GO bit is unset. Data sheet is here: http://www.ti.com/product/drv2667 Signed-off-by: Dan Murphy --- drivers/input/misc/Kconfig | 11 + drivers/input/misc/Makefile | 1 + drivers/input/misc/drv2667.c | 500 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 512 insertions(+) create mode 100644 drivers/input/misc/drv2667.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 41d0ae6..51891f6 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -687,4 +687,15 @@ config INPUT_DRV260X_HAPTICS To compile this driver as a module, choose M here: the module will be called drv260x-haptics. +config INPUT_DRV2667_HAPTICS + tristate "TI DRV2667 haptics support" + depends on INPUT && I2C + select INPUT_FF_MEMLESS + select REGMAP_I2C + help + Say Y to enable support for the TI DRV2667 haptics driver. + + To compile this driver as a module, choose M here: the + module will be called drv260x-haptics. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index cd4bc2d..e0cee17 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.o +obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o diff --git a/drivers/input/misc/drv2667.c b/drivers/input/misc/drv2667.c new file mode 100644 index 0000000..0f43758 --- /dev/null +++ b/drivers/input/misc/drv2667.c @@ -0,0 +1,500 @@ +/* + * DRV2667 haptics driver family + * + * Author: Dan Murphy + * + * Copyright: (C) 2014 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Contol registers */ +#define DRV2667_STATUS 0x00 +#define DRV2667_CTRL_1 0x01 +#define DRV2667_CTRL_2 0x02 +/* Waveform sequencer */ +#define DRV2667_WV_SEQ_0 0x03 +#define DRV2667_WV_SEQ_1 0x04 +#define DRV2667_WV_SEQ_2 0x05 +#define DRV2667_WV_SEQ_3 0x06 +#define DRV2667_WV_SEQ_4 0x07 +#define DRV2667_WV_SEQ_5 0x08 +#define DRV2667_WV_SEQ_6 0x09 +#define DRV2667_WV_SEQ_7 0x0A +#define DRV2667_FIFO 0x0B +#define DRV2667_PAGE 0xFF +#define DRV2667_MAX_REG DRV2667_PAGE + +#define DRV2667_PAGE_0 0x00 +#define DRV2667_PAGE_1 0x01 +#define DRV2667_PAGE_2 0x02 +#define DRV2667_PAGE_3 0x03 +#define DRV2667_PAGE_4 0x04 +#define DRV2667_PAGE_5 0x05 +#define DRV2667_PAGE_6 0x06 +#define DRV2667_PAGE_7 0x07 +#define DRV2667_PAGE_8 0x08 + +/* RAM fields */ +#define DRV2667_RAM_HDR_SZ 0x0 +/* RAM Header addresses */ +#define DRV2667_RAM_START_HI 0x01 +#define DRV2667_RAM_START_LO 0x02 +#define DRV2667_RAM_STOP_HI 0x03 +#define DRV2667_RAM_STOP_LO 0x04 +#define DRV2667_RAM_REPEAT_CT 0x05 +/* RAM data addresses */ +#define DRV2667_RAM_AMP 0x06 +#define DRV2667_RAM_FREQ 0x07 +#define DRV2667_RAM_DURATION 0x08 +#define DRV2667_RAM_ENVELOPE 0x09 + +/* Control 1 Register */ +#define DRV2667_25_VPP_GAIN 0x00 +#define DRV2667_50_VPP_GAIN 0x01 +#define DRV2667_75_VPP_GAIN 0x02 +#define DRV2667_100_VPP_GAIN 0x03 +#define DRV2667_DIGITAL_IN 0xfc +#define DRV2667_ANALOG_IN (1 << 2) + +/* Control 2 Register */ +#define DRV2667_GO (1 << 0) +#define DRV2667_STANDBY (1 << 6) +#define DRV2667_DEV_RST (1 << 7) + +/* RAM Envelope settings */ +#define DRV2667_NO_ENV 0x00 +#define DRV2667_32_MS_ENV 0x01 +#define DRV2667_64_MS_ENV 0x02 +#define DRV2667_96_MS_ENV 0x03 +#define DRV2667_128_MS_ENV 0x04 +#define DRV2667_160_MS_ENV 0x05 +#define DRV2667_192_MS_ENV 0x06 +#define DRV2667_224_MS_ENV 0x07 +#define DRV2667_256_MS_ENV 0x08 +#define DRV2667_512_MS_ENV 0x09 +#define DRV2667_768_MS_ENV 0x0a +#define DRV2667_1024_MS_ENV 0x0b +#define DRV2667_1280_MS_ENV 0x0c +#define DRV2667_1536_MS_ENV 0x0d +#define DRV2667_1792_MS_ENV 0x0e +#define DRV2667_2048_MS_ENV 0x0f + +/** + * struct drv2667_data - + * @input_dev - Pointer to the input device + * @client - Pointer to the I2C client + * @regmap - Register map of the device + * @work - Work item used to off load the enable/disable of the vibration + * @regulator - Pointer to the regulator for the IC + * @magnitude - Magnitude of the vibration event +**/ +struct drv2667_data { + struct input_dev *input_dev; + struct i2c_client *client; + struct regmap *regmap; + struct work_struct work; + struct regulator *regulator; + u32 page; + u32 magnitude; + u32 frequency; +}; + +static struct reg_default drv2667_reg_defs[] = { + { DRV2667_STATUS, 0x02 }, + { DRV2667_CTRL_1, 0x28 }, + { DRV2667_CTRL_2, 0x40 }, + { DRV2667_WV_SEQ_0, 0x00 }, + { DRV2667_WV_SEQ_1, 0x00 }, + { DRV2667_WV_SEQ_2, 0x00 }, + { DRV2667_WV_SEQ_3, 0x00 }, + { DRV2667_WV_SEQ_4, 0x00 }, + { DRV2667_WV_SEQ_5, 0x00 }, + { DRV2667_WV_SEQ_6, 0x00 }, + { DRV2667_WV_SEQ_7, 0x00 }, + { DRV2667_FIFO, 0x00 }, + { DRV2667_PAGE, 0x00 }, +}; + +static int drv2667_set_waveform_freq(struct drv2667_data *haptics) +{ + unsigned int read_buf; + int freq; + int error; + + /* Per the data sheet: + * Sinusoid Frequency (Hz) = 7.8125 x Frequency + */ + freq = (haptics->frequency * 1000) / 78125; + if (freq <= 0) { + dev_err(&haptics->client->dev, + "ERROR: Frequency calculated to %i\n", freq); + return -EINVAL; + } + + error = regmap_read(haptics->regmap, DRV2667_PAGE, &read_buf); + if (error) { + dev_err(&haptics->client->dev, + "Failed to read the page number: %d\n", error); + return -EIO; + } + + if (read_buf == DRV2667_PAGE_0 || + haptics->page != read_buf) { + error = regmap_write(haptics->regmap, + DRV2667_PAGE, haptics->page); + if (error) { + dev_err(&haptics->client->dev, + "Failed to set the page: %d\n", error); + return -EIO; + } + } + + error = regmap_write(haptics->regmap, DRV2667_RAM_FREQ, freq); + if (error) + dev_err(&haptics->client->dev, + "Failed to set the frequency: %d\n", error); + + /* Reset back to original page */ + if (read_buf == DRV2667_PAGE_0 || + haptics->page != read_buf) { + error = regmap_write(haptics->regmap, DRV2667_PAGE, read_buf); + if (error) { + dev_err(&haptics->client->dev, + "Failed to set the page: %d\n", error); + return -EIO; + } + } + + return error; +} + +static void drv2667_worker(struct work_struct *work) +{ + struct drv2667_data *haptics = container_of(work, struct drv2667_data, work); + int error; + + if (haptics->magnitude) { + error = regmap_write(haptics->regmap, + DRV2667_PAGE, haptics->page); + if (error) { + dev_err(&haptics->client->dev, + "Failed to set the page: %d\n", error); + return; + } + + error = regmap_write(haptics->regmap, DRV2667_RAM_AMP, + haptics->magnitude); + if (error) { + dev_err(&haptics->client->dev, + "Failed to set the amplitude: %d\n", error); + return; + } + + error = regmap_write(haptics->regmap, + DRV2667_PAGE, DRV2667_PAGE_0); + if (error) { + dev_err(&haptics->client->dev, + "Failed to set the page: %d\n", error); + return; + } + + error = regmap_write(haptics->regmap, + DRV2667_CTRL_2, DRV2667_GO); + if (error) { + dev_err(&haptics->client->dev, + "Failed to set the GO bit: %d\n", error); + } + } else { + error = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2, + DRV2667_GO, 0); + if (error) { + dev_err(&haptics->client->dev, + "Failed to unset the GO bit: %d\n", error); + } + } +} + +static int drv2667_haptics_play(struct input_dev *input, void *data, + struct ff_effect *effect) +{ + struct drv2667_data *haptics = input_get_drvdata(input); + + if (effect->u.rumble.strong_magnitude > 0) + haptics->magnitude = effect->u.rumble.strong_magnitude; + else if (effect->u.rumble.weak_magnitude > 0) + haptics->magnitude = effect->u.rumble.weak_magnitude; + else + haptics->magnitude = 0; + + schedule_work(&haptics->work); + + return 0; +} + +static void drv2667_close(struct input_dev *input) +{ + struct drv2667_data *haptics = input_get_drvdata(input); + int error; + + cancel_work_sync(&haptics->work); + + error = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2, + DRV2667_STANDBY, 1); + if (error) + dev_err(&haptics->client->dev, + "Failed to enter standby mode: %d\n", error); +} + +static const struct reg_default drv2667_init_regs[] = { + { DRV2667_CTRL_2, 0 }, + { DRV2667_CTRL_1, DRV2667_25_VPP_GAIN }, + { DRV2667_WV_SEQ_0, 1 }, + { DRV2667_WV_SEQ_1, 0 } +}; + +static const struct reg_default drv2667_page1_init[] = { + { DRV2667_RAM_HDR_SZ, 0x05 }, + { DRV2667_RAM_START_HI, 0x80 }, + { DRV2667_RAM_START_LO, 0x06 }, + { DRV2667_RAM_STOP_HI, 0x00 }, + { DRV2667_RAM_STOP_LO, 0x09 }, + { DRV2667_RAM_REPEAT_CT, 0 }, + { DRV2667_RAM_DURATION, 0x05 }, + { DRV2667_RAM_ENVELOPE, DRV2667_NO_ENV }, + { DRV2667_RAM_AMP, 0x60 }, +}; + +static int drv2667_init(struct drv2667_data *haptics) +{ + int error; + + /* Set default haptic frequency to 195Hz on Page 1*/ + haptics->frequency = 195; + haptics->page = DRV2667_PAGE_1; + + error = regmap_register_patch(haptics->regmap, + drv2667_init_regs, + ARRAY_SIZE(drv2667_init_regs)); + if (error) { + dev_err(&haptics->client->dev, + "Failed to write init registers: %d\n", + error); + return error; + } + + error = regmap_write(haptics->regmap, DRV2667_PAGE, haptics->page); + if (error) { + dev_err(&haptics->client->dev, "Failed to set page: %d\n", + error); + goto error_out; + } + + error = drv2667_set_waveform_freq(haptics); + if (error) + goto error_page; + + error = regmap_register_patch(haptics->regmap, + drv2667_page1_init, + ARRAY_SIZE(drv2667_page1_init)); + if (error) { + dev_err(&haptics->client->dev, + "Failed to write page registers: %d\n", + error); + return error; + } + + error = regmap_write(haptics->regmap, DRV2667_PAGE, DRV2667_PAGE_0); + return error; + +error_page: + regmap_write(haptics->regmap, DRV2667_PAGE, DRV2667_PAGE_0); +error_out: + return error; +} + +static const struct regmap_config drv2667_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = DRV2667_MAX_REG, + .reg_defaults = drv2667_reg_defs, + .num_reg_defaults = ARRAY_SIZE(drv2667_reg_defs), + .cache_type = REGCACHE_NONE, +}; + +static int drv2667_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct drv2667_data *haptics; + int error; + + haptics = devm_kzalloc(&client->dev, sizeof(*haptics), GFP_KERNEL); + if (!haptics) + return -ENOMEM; + + haptics->regulator = devm_regulator_get(&client->dev, "vbat"); + if (IS_ERR(haptics->regulator)) { + error = PTR_ERR(haptics->regulator); + dev_err(&client->dev, + "unable to get regulator, error: %d\n", error); + return error; + } + + haptics->input_dev = devm_input_allocate_device(&client->dev); + if (!haptics->input_dev) { + dev_err(&client->dev, "Failed to allocate input device\n"); + return -ENOMEM; + } + + haptics->input_dev->name = "drv2667:haptics"; + haptics->input_dev->dev.parent = client->dev.parent; + haptics->input_dev->close = drv2667_close; + input_set_drvdata(haptics->input_dev, haptics); + input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE); + + error = input_ff_create_memless(haptics->input_dev, NULL, + drv2667_haptics_play); + if (error) { + dev_err(&client->dev, "input_ff_create() failed: %d\n", + error); + return error; + } + + INIT_WORK(&haptics->work, drv2667_worker); + + haptics->client = client; + i2c_set_clientdata(client, haptics); + + haptics->regmap = devm_regmap_init_i2c(client, &drv2667_regmap_config); + if (IS_ERR(haptics->regmap)) { + error = PTR_ERR(haptics->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + error); + return error; + } + + error = drv2667_init(haptics); + if (error) { + dev_err(&client->dev, "Device init failed: %d\n", error); + return error; + } + + error = input_register_device(haptics->input_dev); + if (error) { + dev_err(&client->dev, "couldn't register input device: %d\n", + error); + return error; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int drv2667_suspend(struct device *dev) +{ + struct drv2667_data *haptics = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&haptics->input_dev->mutex); + + if (haptics->input_dev->users) { + ret = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2, + DRV2667_STANDBY, 1); + if (ret) { + dev_err(dev, "Failed to set standby mode\n"); + regulator_disable(haptics->regulator); + goto out; + } + + ret = regulator_disable(haptics->regulator); + if (ret) { + dev_err(dev, "Failed to disable regulator\n"); + regmap_update_bits(haptics->regmap, + DRV2667_CTRL_2, + DRV2667_STANDBY, 0); + } + } +out: + mutex_unlock(&haptics->input_dev->mutex); + return ret; +} + +static int drv2667_resume(struct device *dev) +{ + struct drv2667_data *haptics = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&haptics->input_dev->mutex); + + if (haptics->input_dev->users) { + ret = regulator_enable(haptics->regulator); + if (ret) { + dev_err(dev, "Failed to enable regulator\n"); + goto out; + } + + ret = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2, + DRV2667_STANDBY, 0); + if (ret) { + dev_err(dev, "Failed to unset standby mode\n"); + regulator_disable(haptics->regulator); + goto out; + } + + } + +out: + mutex_unlock(&haptics->input_dev->mutex); + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(drv2667_pm_ops, drv2667_suspend, drv2667_resume); + +static const struct i2c_device_id drv2667_id[] = { + { "drv2667", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, drv2667_id); + +#ifdef CONFIG_OF +static const struct of_device_id drv2667_of_match[] = { + { .compatible = "ti,drv2667", }, + { } +}; +MODULE_DEVICE_TABLE(of, drv2667_of_match); +#endif + +static struct i2c_driver drv2667_driver = { + .probe = drv2667_probe, + .driver = { + .name = "drv2667-haptics", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(drv2667_of_match), + .pm = &drv2667_pm_ops, + }, + .id_table = drv2667_id, +}; +module_i2c_driver(drv2667_driver); + +MODULE_ALIAS("platform:drv2667-haptics"); +MODULE_DESCRIPTION("TI DRV2667 haptics driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dan Murphy ");