* [PATCH V6 3/3] Input: new da7280 haptic driver
From: Roy Im @ 2018-09-11 12:18 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring
Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
linux-kernel
In-Reply-To: <cover.1536668286.git.Roy.Im@diasemi.com>
Adds support for the Dialog DA7280 LRA/ERM Haptic Driver with
multiple mode and integrated waveform memory and wideband support.
It communicates via an I2C bus to the device.
Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>
---
v6: No changes.
v5: Fixed errors in Kconfig file.
v4: Updated code as dt-bindings are changed.
v3: No changes.
v2: Fixed kbuild error/warning
drivers/input/misc/Kconfig | 13 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/da7280.c | 1438 +++++++++++++++++++++++++++++++++++++++++++
drivers/input/misc/da7280.h | 412 +++++++++++++
4 files changed, 1864 insertions(+)
create mode 100644 drivers/input/misc/da7280.c
create mode 100644 drivers/input/misc/da7280.h
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index ca59a2b..751cac6 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -851,4 +851,17 @@ config INPUT_SC27XX_VIBRA
To compile this driver as a module, choose M here. The module will
be called sc27xx_vibra.
+config INPUT_DA7280_HAPTICS
+ tristate "Dialog Semiconductor DA7280 haptics support"
+ depends on INPUT && I2C
+ select INPUT_FF_MEMLESS
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the Dialog DA7280 haptics driver.
+ The haptics can be controlled by i2c communication,
+ or by PWM input, or by GPI.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da7280.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 9d0f9d1..d941348 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o
obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o
obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
obj-$(CONFIG_INPUT_DA9063_ONKEY) += da9063_onkey.o
diff --git a/drivers/input/misc/da7280.c b/drivers/input/misc/da7280.c
new file mode 100644
index 0000000..041a9f4
--- /dev/null
+++ b/drivers/input/misc/da7280.c
@@ -0,0 +1,1438 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DA7280 Haptic device driver
+ *
+ * Copyright (c) 2018 Dialog Semiconductor.
+ * Author: Roy Im <Roy.Im.Opensource@diasemi.com>
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include "da7280.h"
+
+/* uV unit for voltage rate */
+#define DA7280_VOLTAGE_RATE_MAX 6000000
+#define DA7280_VOLTAGE_RATE_STEP 23400
+#define DA7280_NOMMAX_DFT 0x6B
+#define DA7280_ABSMAX_DFT 0x78
+
+#define DA7280_IMPD_MAX 1500000000
+#define DA7280_IMPD_DEFAULT 22000000
+
+#define DA7280_IMAX_DEFAULT 0x0E
+/* uA unit step and limit for IMAX*/
+#define DA7280_IMAX_STEP 7200
+#define DA7280_IMAX_LIMIT 252000
+
+#define DA7280_RESONT_FREQH_DFT 0x39
+#define DA7280_RESONT_FREQL_DFT 0x32
+#define DA7280_MIN_RESONAT_FREQ_HZ 50
+#define DA7280_MAX_RESONAT_FREQ_HZ 300
+#define DA7280_MIN_PWM_FREQ_KHZ 10
+#define DA7280_MAX_PWM_FREQ_KHZ 250
+
+#define DA7280_SEQ_ID_MAX 15
+#define DA7280_SEQ_LOOP_MAX 15
+#define DA7280_GPI1_SEQ_ID_DEFT 0x0
+
+#define DA7280_SNP_MEM_SIZE 100
+#define DA7280_SNP_MEM_MAX DA7280_SNP_MEM_99
+
+#define IRQ_NUM 3
+
+#define DA7280_SKIP_INIT 0x100
+
+enum da7280_haptic_dev_t {
+ DA7280_LRA = 0,
+ DA7280_ERM_BAR = 1,
+ DA7280_ERM_COIN = 2,
+ DA7280_DEV_MAX,
+};
+
+enum da7280_op_mode {
+ DA7280_INACTIVE = 0,
+ DA7280_DRO_MODE = 1,
+ DA7280_PWM_MODE = 2,
+ DA7280_RTWM_MODE = 3,
+ DA7280_ETWM_MODE = 4,
+ DA7280_OPMODE_MAX,
+};
+
+struct da7280_gpi_ctl {
+ u8 seq_id;
+ u8 mode;
+ u8 polarity;
+};
+
+struct da7280_haptic {
+ struct regmap *regmap;
+ struct input_dev *input_dev;
+ struct device *dev;
+ struct i2c_client *client;
+ struct pwm_device *pwm_dev;
+ bool legacy;
+ int pwm_id;
+ struct work_struct work;
+
+ bool suspend_state;
+ unsigned int magnitude;
+
+ u8 dev_type;
+ u8 op_mode;
+ u16 nommax;
+ u16 absmax;
+ u32 imax;
+ u32 impd;
+ u32 resonant_freq_h;
+ u32 resonant_freq_l;
+ u8 bemf_sense_en;
+ u8 freq_track_en;
+ u8 acc_en;
+ u8 rapid_stop_en;
+ u8 amp_pid_en;
+ u8 ps_seq_id;
+ u8 ps_seq_loop;
+ struct da7280_gpi_ctl gpi_ctl[3];
+ bool mem_update;
+ u8 snp_mem[DA7280_SNP_MEM_SIZE];
+ const struct attribute_group **attr_group;
+};
+
+static bool da7280_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case DA7280_IRQ_EVENT1:
+ case DA7280_IRQ_EVENT_WARNING_DIAG:
+ case DA7280_IRQ_EVENT_SEQ_DIAG:
+ case DA7280_IRQ_STATUS1:
+ case DA7280_TOP_CTL1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config da7280_haptic_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = DA7280_SNP_MEM_MAX,
+ .volatile_reg = da7280_volatile_register,
+};
+
+static int da7280_haptic_mem_update(struct da7280_haptic *haptics)
+{
+ int ret;
+ unsigned int val;
+
+ /* It is recommended to update the patterns
+ * during haptic is not working in order to avoid conflict
+ */
+ ret = regmap_read(haptics->regmap, DA7280_IRQ_STATUS1, &val);
+ if (ret)
+ return ret;
+ if (val & DA7280_STA_WARNING_MASK) {
+ dev_warn(haptics->dev,
+ "Warning! Please check HAPTIC status.\n");
+ return -EBUSY;
+ }
+
+ /* Patterns are not updated if the lock bit is enabled */
+ val = 0;
+ ret = regmap_read(haptics->regmap, DA7280_MEM_CTL2, &val);
+ if (ret)
+ return ret;
+ if (~val & DA7280_WAV_MEM_LOCK_MASK) {
+ dev_warn(haptics->dev,
+ "Please unlock the bit first\n");
+ return -EACCES;
+ }
+
+ /* Set to Inactive mode to make sure safety */
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK,
+ 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(haptics->regmap, DA7280_MEM_CTL1, &val);
+ if (ret)
+ return ret;
+
+ return regmap_bulk_write(haptics->regmap, val,
+ haptics->snp_mem, DA7280_SNP_MEM_MAX - val + 1);
+}
+
+static int da7280_haptic_set_pwm(struct da7280_haptic *haptics)
+{
+ struct pwm_args pargs;
+ u64 period_mag_multi;
+ unsigned int pwm_duty;
+ int ret;
+
+ pwm_get_args(haptics->pwm_dev, &pargs);
+ period_mag_multi =
+ (u64)(pargs.period * haptics->magnitude);
+ if (haptics->acc_en)
+ pwm_duty =
+ (unsigned int)(period_mag_multi >> 16);
+ else
+ pwm_duty =
+ (unsigned int)((period_mag_multi >> 16)
+ + pargs.period) / 2;
+
+ ret = pwm_config(haptics->pwm_dev,
+ pwm_duty, pargs.period);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to configure pwm : %d\n", ret);
+ return ret;
+ }
+
+ ret = pwm_enable(haptics->pwm_dev);
+ if (ret) {
+ pwm_disable(haptics->pwm_dev);
+ dev_err(haptics->dev,
+ "failed to enable haptics pwm device : %d\n", ret);
+ }
+
+ return ret;
+}
+
+static void da7280_haptic_enable(struct da7280_haptic *haptics)
+{
+ int ret = 0;
+
+ switch (haptics->op_mode) {
+ case DA7280_DRO_MODE:
+ /* the valid range check when acc_en is enabled */
+ if (haptics->acc_en && haptics->magnitude > 0x7F)
+ haptics->magnitude = 0x7F;
+ else if (haptics->magnitude > 0xFF)
+ haptics->magnitude = 0xFF;
+
+ /* Set driver level
+ * as a % of ACTUATOR_NOMMAX(nommax)
+ */
+ ret = regmap_write(haptics->regmap,
+ DA7280_TOP_CTL2,
+ haptics->magnitude);
+ if (ret) {
+ dev_err(haptics->dev,
+ "i2c err for driving level set : %d\n",
+ ret);
+ return;
+ }
+ break;
+ case DA7280_PWM_MODE:
+ if (da7280_haptic_set_pwm(haptics))
+ return;
+ break;
+ case DA7280_RTWM_MODE:
+ /* PS_SEQ_ID will be played
+ * as many times as the PS_SEQ_LOOP
+ */
+ case DA7280_ETWM_MODE:
+ /* Now users are able to control the GPI(N)
+ * assigned to GPI_0, GPI1 and GPI2 accordingly
+ * please see the datasheet for details.
+ * GPI(N)_SEQUENCE_ID will be played
+ * as many times as the PS_SEQ_LOOP
+ */
+ break;
+ default:
+ dev_err(haptics->dev,
+ "Invalid Mode(%d)\n", haptics->op_mode);
+ return;
+ }
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK,
+ haptics->op_mode);
+ if (ret) {
+ dev_err(haptics->dev,
+ "i2c err for op_mode setting : %d\n", ret);
+ return;
+ }
+
+ if (haptics->op_mode == DA7280_PWM_MODE ||
+ haptics->op_mode == DA7280_RTWM_MODE) {
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_SEQ_START_MASK,
+ DA7280_SEQ_START_MASK);
+ if (ret)
+ dev_err(haptics->dev,
+ "i2c err for sequence triggering : %d\n", ret);
+ }
+}
+
+static void da7280_haptic_disable(struct da7280_haptic *haptics)
+{
+ int ret;
+
+ /* Set to Inactive mode */
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (ret) {
+ dev_err(haptics->dev,
+ "i2c err for op_mode off : %d\n", ret);
+ return;
+ }
+
+ switch (haptics->op_mode) {
+ case DA7280_RTWM_MODE:
+ case DA7280_ETWM_MODE:
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_SEQ_START_MASK, 0);
+ if (ret) {
+ dev_err(haptics->dev,
+ "i2c err for RTWM or ETWM mode off : %d\n",
+ ret);
+ return;
+ }
+ break;
+ case DA7280_DRO_MODE:
+ ret = regmap_write(haptics->regmap,
+ DA7280_TOP_CTL2, 0);
+ if (ret) {
+ dev_err(haptics->dev,
+ "i2c err for DRO mode off : %d\n",
+ ret);
+ return;
+ }
+ break;
+ case DA7280_PWM_MODE:
+ pwm_disable(haptics->pwm_dev);
+ break;
+ default:
+ dev_err(haptics->dev,
+ "Invalid Mode(%d)\n", haptics->op_mode);
+ break;
+ }
+}
+
+static void da7280_haptic_work(struct work_struct *work)
+{
+ struct da7280_haptic *haptics =
+ container_of(work, struct da7280_haptic, work);
+
+ if (haptics->magnitude)
+ da7280_haptic_enable(haptics);
+ else
+ da7280_haptic_disable(haptics);
+}
+
+static int da7280_haptic_play(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+
+ 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 int da7280_haptic_open(struct input_dev *dev)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (ret)
+ dev_err(haptics->dev,
+ "Failed to open haptic, i2c error : %d\n", ret);
+
+ return ret;
+}
+
+static void da7280_haptic_close(struct input_dev *dev)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+ int ret;
+
+ cancel_work_sync(&haptics->work);
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (ret)
+ goto error_i2c;
+
+ if (haptics->op_mode == DA7280_DRO_MODE) {
+ ret = regmap_write(haptics->regmap,
+ DA7280_TOP_CTL2, 0);
+
+ if (ret)
+ goto error_i2c;
+ }
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK, 0);
+ if (ret)
+ goto error_i2c;
+
+ return;
+
+error_i2c:
+ dev_err(haptics->dev, "DA7280-haptic i2c error : %d\n", ret);
+}
+
+static u8 da7280_haptic_of_mode_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "LRA"))
+ return DA7280_LRA;
+ else if (!strcmp(str, "ERM-bar"))
+ return DA7280_ERM_BAR;
+ else if (!strcmp(str, "ERM-coin"))
+ return DA7280_ERM_COIN;
+
+ dev_warn(dev, "Invalid string - set to default\n");
+ return DA7280_LRA;
+}
+
+static u8 da7280_haptic_of_gpi_mode_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "Single-pattern"))
+ return 0;
+ else if (!strcmp(str, "Multi-pattern"))
+ return 1;
+
+ dev_warn(dev, "Invalid string - set to default\n");
+ return 0;
+}
+
+static u8 da7280_haptic_of_gpi_pol_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "Rising-edge"))
+ return 0;
+ else if (!strcmp(str, "Falling-edge"))
+ return 1;
+ else if (!strcmp(str, "Both-edge"))
+ return 2;
+
+ dev_warn(dev, "Invalid string - set to default\n");
+ return 0;
+}
+
+static u8 da7280_haptic_of_volt_rating_set(u32 val)
+{
+ u32 voltage;
+
+ voltage = val / DA7280_VOLTAGE_RATE_STEP + 1;
+
+ if (voltage > 0xFF)
+ return 0xFF;
+ return (u8)voltage;
+}
+
+static void da7280_of_to_pdata(struct device *dev,
+ struct da7280_haptic *haptics)
+{
+ struct device_node *np = dev->of_node;
+ char dt_gpi_str1[] = "dlg,gpi0-seq-id";
+ char dt_gpi_str2[] = "dlg,gpi0-mode";
+ char dt_gpi_str3[] = "dlg,gpi0-polarity";
+ unsigned int mem[DA7280_SNP_MEM_SIZE];
+ const char *of_str;
+ u32 of_val32;
+ int i;
+
+ if (!of_property_read_string(np, "dlg,actuator-type", &of_str))
+ haptics->dev_type =
+ da7280_haptic_of_mode_str(dev, of_str);
+ else /* if no property, then use the mode inside chip */
+ haptics->dev_type = DA7280_DEV_MAX;
+
+ if (of_property_read_u32(np, "dlg,op-mode", &of_val32) >= 0)
+ if (of_val32 && of_val32 < DA7280_OPMODE_MAX)
+ haptics->op_mode = of_val32;
+ else
+ haptics->op_mode = DA7280_DRO_MODE;
+ else
+ haptics->op_mode = DA7280_DRO_MODE;
+
+ if (of_property_read_u32(np, "dlg,nom-microvolt", &of_val32) >= 0)
+ if (of_val32 < DA7280_VOLTAGE_RATE_MAX)
+ haptics->nommax =
+ da7280_haptic_of_volt_rating_set(of_val32);
+ else
+ haptics->nommax = DA7280_SKIP_INIT;
+ else /* if no property, then use the value inside chip */
+ haptics->nommax = DA7280_SKIP_INIT;
+
+ if (of_property_read_u32(np, "dlg,abs-max-microvolt", &of_val32) >= 0)
+ if (of_val32 < DA7280_VOLTAGE_RATE_MAX)
+ haptics->absmax =
+ da7280_haptic_of_volt_rating_set(of_val32);
+ else
+ haptics->absmax = DA7280_SKIP_INIT;
+ else
+ haptics->absmax = DA7280_SKIP_INIT;
+
+ if (of_property_read_u32(np, "dlg,imax-microamp", &of_val32) >= 0)
+ if (of_val32 < DA7280_IMAX_LIMIT)
+ haptics->imax = (of_val32 - 28600)
+ / DA7280_IMAX_STEP + 1;
+ else
+ haptics->imax = DA7280_IMAX_DEFAULT;
+ else
+ haptics->imax = DA7280_IMAX_DEFAULT;
+
+ if (of_property_read_u32(np, "dlg,impd-micro-ohms", &of_val32) >= 0)
+ if (of_val32 <= DA7280_IMPD_MAX)
+ haptics->impd = of_val32;
+ else
+ haptics->impd = DA7280_IMPD_DEFAULT;
+ else
+ haptics->impd = DA7280_IMPD_DEFAULT;
+
+ if (of_property_read_u32(np, "dlg,resonant-freq-hz", &of_val32) >= 0) {
+ if (of_val32 < DA7280_MAX_RESONAT_FREQ_HZ &&
+ of_val32 > DA7280_MIN_RESONAT_FREQ_HZ) {
+ haptics->resonant_freq_h =
+ ((1000000000 / (of_val32 * 1333)) >> 7) & 0xFF;
+ haptics->resonant_freq_l =
+ (1000000000 / (of_val32 * 1333)) & 0x7F;
+ } else {
+ haptics->resonant_freq_h =
+ DA7280_RESONT_FREQH_DFT;
+ haptics->resonant_freq_l =
+ DA7280_RESONT_FREQL_DFT;
+ }
+ } else {
+ haptics->resonant_freq_h = DA7280_SKIP_INIT;
+ haptics->resonant_freq_l = DA7280_SKIP_INIT;
+ }
+
+ if (of_property_read_u32(np, "dlg,ps-seq-id", &of_val32) >= 0)
+ if (of_val32 <= DA7280_SEQ_ID_MAX)
+ haptics->ps_seq_id = of_val32;
+ else
+ haptics->ps_seq_id = 0;
+ else /* if no property, set to zero as a default do nothing */
+ haptics->ps_seq_id = 0;
+
+ if (of_property_read_u32(np, "dlg,ps-seq-loop", &of_val32) >= 0)
+ if (of_val32 <= DA7280_SEQ_LOOP_MAX)
+ haptics->ps_seq_loop = of_val32;
+ else
+ haptics->ps_seq_loop = 0;
+ else /* if no property, then do nothing */
+ haptics->ps_seq_loop = 0;
+
+ /* GPI0~2 Control */
+ for (i = 0; i < 3; i++) {
+ dt_gpi_str1[7] = '0' + i;
+ if (of_property_read_u32(np, dt_gpi_str1, &of_val32) >= 0)
+ if (of_val32 <= DA7280_SEQ_ID_MAX)
+ haptics->gpi_ctl[i].seq_id = of_val32;
+ else
+ haptics->gpi_ctl[i].seq_id =
+ DA7280_GPI1_SEQ_ID_DEFT + i;
+ else /* if no property, then do nothing */
+ haptics->gpi_ctl[i].seq_id =
+ DA7280_GPI1_SEQ_ID_DEFT + i;
+
+ dt_gpi_str2[7] = '0' + i;
+ if (!of_property_read_string(np, dt_gpi_str2, &of_str))
+ haptics->gpi_ctl[i].mode =
+ da7280_haptic_of_gpi_mode_str(dev, of_str);
+ else
+ haptics->gpi_ctl[i].mode = 0;
+
+ dt_gpi_str3[7] = '0' + i;
+ if (!of_property_read_string(np, dt_gpi_str3, &of_str))
+ haptics->gpi_ctl[i].polarity =
+ da7280_haptic_of_gpi_pol_str(dev, of_str);
+ else
+ haptics->gpi_ctl[i].polarity = 0;
+ }
+
+ haptics->bemf_sense_en =
+ of_property_read_bool(np, "dlg,bemf-sens-enable");
+ haptics->freq_track_en =
+ of_property_read_bool(np, "dlg,freq-track-enable");
+ haptics->acc_en =
+ of_property_read_bool(np, "dlg,acc-enable");
+ haptics->rapid_stop_en =
+ of_property_read_bool(np, "dlg,rapid-stop-enable");
+ haptics->amp_pid_en =
+ of_property_read_bool(np, "dlg,amp-pid-enable");
+
+ if (of_property_read_u32_array(np, "dlg,mem-array",
+ &mem[0], DA7280_SNP_MEM_SIZE) >= 0) {
+ haptics->mem_update = 1;
+ for (i = 0; i < DA7280_SNP_MEM_SIZE; i++) {
+ if (mem[i] > 0xff)
+ haptics->snp_mem[i] = 0x0;
+ else
+ haptics->snp_mem[i] = (u8)mem[i];
+ }
+ } else {
+ haptics->mem_update = 0;
+ }
+}
+
+static irqreturn_t da7280_irq_handler(int irq, void *data)
+{
+ struct da7280_haptic *haptics = data;
+ u8 events[IRQ_NUM];
+ int ret;
+
+ /* Check what events have happened */
+ ret = regmap_bulk_read(haptics->regmap,
+ DA7280_IRQ_EVENT1,
+ events, IRQ_NUM);
+ if (ret)
+ goto error_i2c;
+
+ /* Empty check due to shared interrupt */
+ if ((events[0] | events[1] | events[2]) == 0x00)
+ return IRQ_HANDLED;
+
+ if (events[0] & DA7280_E_SEQ_FAULT_MASK) {
+ /* Stop first if Haptic is working
+ * Otherwise, the fault may happen continually
+ * even though the bit is cleared.
+ */
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (ret)
+ goto error_i2c;
+ }
+
+ /* Clear events */
+ ret = regmap_write(haptics->regmap,
+ DA7280_IRQ_EVENT1, events[0]);
+ if (ret)
+ goto error_i2c;
+
+ return IRQ_HANDLED;
+
+error_i2c:
+ dev_err(haptics->dev, "da7280 i2c error : %d\n", ret);
+ return IRQ_NONE;
+}
+
+static int da7280_init(struct da7280_haptic *haptics)
+{
+ int ret, i;
+ unsigned int val = 0;
+ u32 v2i_factor;
+ u8 mask = 0;
+
+ /* If device type is DA7280_DEV_MAX,
+ * then just use default value inside chip.
+ */
+ if (haptics->dev_type == DA7280_DEV_MAX) {
+ ret = regmap_read(haptics->regmap, DA7280_TOP_CFG1, &val);
+ if (ret)
+ goto error_i2c;
+ if (val & DA7280_ACTUATOR_TYPE_MASK)
+ haptics->dev_type = DA7280_ERM_COIN;
+ else
+ haptics->dev_type = DA7280_LRA;
+ }
+
+ /* Apply user settings */
+ if (haptics->dev_type == DA7280_LRA) {
+ if (haptics->resonant_freq_l != DA7280_SKIP_INIT) {
+ ret = regmap_write(haptics->regmap,
+ DA7280_FRQ_LRA_PER_H,
+ haptics->resonant_freq_h);
+ if (ret)
+ goto error_i2c;
+ ret = regmap_write(haptics->regmap,
+ DA7280_FRQ_LRA_PER_L,
+ haptics->resonant_freq_l);
+ if (ret)
+ goto error_i2c;
+ }
+ } else if (haptics->dev_type == DA7280_ERM_COIN) {
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_INT_CFG1,
+ DA7280_BEMF_FAULT_LIM_MASK, 0);
+ if (ret)
+ goto error_i2c;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CFG4,
+ DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+ DA7280_V2I_FACTOR_FREEZE_MASK,
+ DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+ DA7280_V2I_FACTOR_FREEZE_MASK);
+ if (ret)
+ goto error_i2c;
+
+ haptics->acc_en = 0;
+ haptics->rapid_stop_en = 0;
+ haptics->amp_pid_en = 0;
+ }
+
+ /* Should be set to 0 only
+ * in custom waveform and wideband operation
+ */
+ if (haptics->op_mode >= DA7280_RTWM_MODE)
+ haptics->bemf_sense_en = 0;
+
+ mask = DA7280_ACTUATOR_TYPE_MASK |
+ DA7280_BEMF_SENSE_EN_MASK |
+ DA7280_FREQ_TRACK_EN_MASK |
+ DA7280_ACCELERATION_EN_MASK |
+ DA7280_RAPID_STOP_EN_MASK |
+ DA7280_AMP_PID_EN_MASK;
+
+ val = (haptics->dev_type ? 1 : 0)
+ << DA7280_ACTUATOR_TYPE_SHIFT |
+ (haptics->bemf_sense_en ? 1 : 0)
+ << DA7280_BEMF_SENSE_EN_SHIFT |
+ (haptics->freq_track_en ? 1 : 0)
+ << DA7280_FREQ_TRACK_EN_SHIFT |
+ (haptics->acc_en ? 1 : 0)
+ << DA7280_ACCELERATION_EN_SHIFT |
+ (haptics->rapid_stop_en ? 1 : 0)
+ << DA7280_RAPID_STOP_EN_SHIFT |
+ (haptics->amp_pid_en ? 1 : 0)
+ << DA7280_AMP_PID_EN_SHIFT;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CFG1, mask, val);
+ if (ret)
+ goto error_i2c;
+
+ if (haptics->nommax != DA7280_SKIP_INIT) {
+ ret = regmap_write(haptics->regmap,
+ DA7280_ACTUATOR1,
+ haptics->nommax);
+ if (ret)
+ goto error_i2c;
+ }
+
+ if (haptics->absmax != DA7280_SKIP_INIT) {
+ ret = regmap_write(haptics->regmap, DA7280_ACTUATOR2,
+ haptics->absmax);
+ if (ret)
+ goto error_i2c;
+ }
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_ACTUATOR3,
+ DA7280_IMAX_MASK,
+ haptics->imax);
+ if (ret)
+ goto error_i2c;
+
+ v2i_factor =
+ haptics->impd * (haptics->imax + 4) / 1610400;
+ ret = regmap_write(haptics->regmap,
+ DA7280_CALIB_V2I_L,
+ v2i_factor & 0xff);
+ if (ret)
+ goto error_i2c;
+ ret = regmap_write(haptics->regmap,
+ DA7280_CALIB_V2I_H,
+ v2i_factor >> 8);
+ if (ret)
+ goto error_i2c;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (ret)
+ goto error_i2c;
+
+ if (haptics->mem_update) {
+ ret = da7280_haptic_mem_update(haptics);
+ if (ret)
+ goto error_i2c;
+ }
+
+ /* Set PS_SEQ_ID and PS_SEQ_LOOP */
+ val = haptics->ps_seq_id << DA7280_PS_SEQ_ID_SHIFT |
+ haptics->ps_seq_loop << DA7280_PS_SEQ_LOOP_SHIFT;
+ ret = regmap_write(haptics->regmap,
+ DA7280_SEQ_CTL2, val);
+ if (ret)
+ goto error_i2c;
+
+ /* GPI(N) CTL */
+ for (i = 0; i < 3; i++) {
+ val = haptics->gpi_ctl[i].seq_id
+ << DA7280_GPI0_SEQUENCE_ID_SHIFT |
+ haptics->gpi_ctl[i].mode
+ << DA7280_GPI0_MODE_SHIFT |
+ haptics->gpi_ctl[i].polarity
+ << DA7280_GPI0_POLARITY_SHIFT;
+ ret = regmap_write(haptics->regmap,
+ DA7280_GPI_0_CTL + i, val);
+ if (ret)
+ goto error_i2c;
+ }
+
+ /* Clear Interrupts */
+ ret = regmap_write(haptics->regmap, DA7280_IRQ_EVENT1, 0xff);
+ if (ret)
+ goto error_i2c;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_IRQ_MASK1,
+ DA7280_SEQ_FAULT_M_MASK
+ | DA7280_SEQ_DONE_M_MASK, 0);
+ if (ret)
+ goto error_i2c;
+
+ haptics->suspend_state = 0;
+ return 0;
+
+error_i2c:
+ dev_err(haptics->dev, "haptic init - I2C error : %d\n", ret);
+ return ret;
+}
+
+/* Valid format for ps_seq_id
+ * echo X > ps_seq_id
+ * ex) echo 2 > /sys/class/..../ps_seq_id
+ * 0 <= X <= 15.
+ */
+static ssize_t ps_seq_id_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ long val = 0xff;
+ int ret;
+
+ if (kstrtol(&buf[0], 0, &val) < 0)
+ goto err;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_SEQ_CTL2,
+ DA7280_PS_SEQ_ID_MASK,
+ (val & 0xf) >> DA7280_PS_SEQ_ID_SHIFT);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", ret);
+ return ret;
+ }
+
+ haptics->ps_seq_id = val & 0xf;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid input\n");
+ return count;
+}
+
+static ssize_t ps_seq_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(haptics->regmap, DA7280_SEQ_CTL2, &val);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", ret);
+ return ret;
+ }
+ val = (val & DA7280_PS_SEQ_ID_MASK)
+ >> DA7280_PS_SEQ_ID_SHIFT;
+
+ return sprintf(buf, "ps_seq_id is %d\n", val);
+}
+
+/* Valid format for ps_seq_loop
+ * echo X > ps_seq_loop
+ * ex) echo 2 > /sys/class/..../ps_seq_loop
+ * 0 <= X <= 15.
+ */
+static ssize_t ps_seq_loop_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ long val = 0xff;
+ int ret;
+
+ if (kstrtol(&buf[0], 0, &val) < 0)
+ goto err;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_SEQ_CTL2,
+ DA7280_PS_SEQ_LOOP_MASK,
+ (val & 0xF) << DA7280_PS_SEQ_LOOP_SHIFT);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", ret);
+ return ret;
+ }
+
+ haptics->ps_seq_loop = (val & 0xF);
+
+ return count;
+err:
+ dev_err(dev, "Invalid input value!\n");
+ return count;
+}
+
+static ssize_t ps_seq_loop_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(haptics->regmap, DA7280_SEQ_CTL2, &val);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", ret);
+ return ret;
+ }
+ val = (val & DA7280_PS_SEQ_LOOP_MASK)
+ >> DA7280_PS_SEQ_LOOP_SHIFT;
+
+ return sprintf(buf, "ps_seq_loop is %d\n", val);
+}
+
+/* Valid format for GPIx_SEQUENCE_ID
+ * echo X Y > gpi_seq_id
+ * ex) echo 2 15 > /sys/class/..../gpi_seq_id
+ * 0 <= X < 3, 0<= Y <= 15.
+ */
+static ssize_t gpi_seq_id_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ u8 gpi_num = 0xff;
+ long seq_id = 0xff;
+ int ret;
+
+ if (count < 4)
+ goto err;
+
+ if (buf[0] >= '0')
+ gpi_num = buf[0] - '0';
+ else
+ goto err;
+
+ if (buf[1] != ' ')
+ goto err;
+
+ if (kstrtol(&buf[2], 0, &seq_id) < 0)
+ goto err;
+
+ if (gpi_num > 2 || seq_id > 0xf)
+ goto err;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_GPI_0_CTL + gpi_num,
+ DA7280_GPI0_SEQUENCE_ID_MASK,
+ seq_id << DA7280_GPI0_SEQUENCE_ID_SHIFT);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", ret);
+ return ret;
+ }
+
+ haptics->gpi_ctl[gpi_num].seq_id = seq_id;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid format or values!\n");
+ return count;
+}
+
+static ssize_t gpi_seq_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int ret;
+ unsigned int val, i;
+
+ for (i = 0; i < 3; i++) {
+ ret = regmap_read(haptics->regmap,
+ DA7280_GPI_0_CTL + i, &val);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", ret);
+ return ret;
+ }
+ haptics->gpi_ctl[i].seq_id =
+ (val & DA7280_GPI0_SEQUENCE_ID_MASK)
+ >> DA7280_GPI0_SEQUENCE_ID_SHIFT;
+ val = 0;
+ }
+
+ return sprintf(buf,
+ "Seq ID\nGPI0 : %d\nGPI1 : %d\nGPI2 : %d\n",
+ haptics->gpi_ctl[0].seq_id,
+ haptics->gpi_ctl[1].seq_id,
+ haptics->gpi_ctl[2].seq_id);
+}
+
+/* Valid format for GPIx_MODE
+ * echo X Y > gpi_mode
+ * ex) echo 2 1 > /sys/class/..../gpi_mode
+ * 0 <= X < 3, 0<= Y <= 1.
+ */
+static ssize_t gpi_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ u8 gpi_num = 0xff, gpi_mode = 0xff;
+ int ret;
+
+ if (count < 3)
+ goto err;
+
+ if (buf[0] >= '0')
+ gpi_num = buf[0] - '0';
+ if (buf[2] >= '0')
+ gpi_mode = buf[2] - '0';
+
+ if (gpi_num > 2 || gpi_mode > 1)
+ goto err;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_GPI_0_CTL + gpi_num,
+ DA7280_GPI0_MODE_MASK,
+ gpi_mode << DA7280_GPI0_MODE_SHIFT);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", ret);
+ return ret;
+ }
+
+ haptics->gpi_ctl[gpi_num].mode = gpi_mode;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid format!\n");
+ return count;
+}
+
+static ssize_t gpi_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int ret;
+ unsigned int val, i;
+
+ for (i = 0; i < 3; i++) {
+ ret = regmap_read(haptics->regmap,
+ DA7280_GPI_0_CTL + i, &val);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", ret);
+ return ret;
+ }
+ haptics->gpi_ctl[i].mode =
+ (val & DA7280_GPI0_MODE_MASK)
+ >> DA7280_GPI0_MODE_SHIFT;
+ val = 0;
+ }
+
+ return sprintf(buf, "Mode\nGPI0 : %d\nGPI1 : %d\nGPI2 : %d\n",
+ haptics->gpi_ctl[0].mode,
+ haptics->gpi_ctl[1].mode,
+ haptics->gpi_ctl[2].mode);
+}
+
+/* Valid format for GPIx_MODE
+ * echo X Y > gpi_pol
+ * ex) echo 2 1 > /sys/class/..../gpi_pol
+ * 0 <= X < 3, 0<= Y <= 2.
+ */
+static ssize_t gpi_pol_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ u8 gpi_pol = 0xff, gpi_num = 0xff;
+ int ret;
+
+ if (count < 3)
+ goto err;
+
+ if (buf[0] >= '0')
+ gpi_num = buf[0] - '0';
+ if (buf[2] >= '0')
+ gpi_pol = buf[2] - '0';
+
+ if (gpi_num > 2 || gpi_pol > 2)
+ goto err;
+
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_GPI_0_CTL + gpi_num,
+ DA7280_GPI0_POLARITY_MASK,
+ gpi_pol << DA7280_GPI0_POLARITY_SHIFT);
+ if (ret) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", ret);
+ return ret;
+ }
+
+ haptics->gpi_ctl[gpi_num].polarity = gpi_pol;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid format or input values!\n");
+ return count;
+}
+
+static ssize_t gpi_pol_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+ unsigned int val, i;
+
+ for (i = 0; i < 3; i++) {
+ ret = regmap_read(haptics->regmap,
+ DA7280_GPI_0_CTL + i, &val);
+ if (ret)
+ return ret;
+ haptics->gpi_ctl[i].polarity =
+ (val & DA7280_GPI0_POLARITY_MASK)
+ >> DA7280_GPI0_POLARITY_SHIFT;
+ val = 0;
+ }
+
+ return sprintf(buf, "Polarity\nGPI0 : %d\nGPI1 : %d\nGPI2 : %d\n",
+ haptics->gpi_ctl[0].polarity,
+ haptics->gpi_ctl[1].polarity,
+ haptics->gpi_ctl[2].polarity);
+}
+
+#define MAX_PTN_REGS DA7280_SNP_MEM_SIZE
+#define MAX_USER_INPUT_LEN (5 * DA7280_SNP_MEM_SIZE)
+struct parse_data_t {
+ int len;
+ u8 val[MAX_PTN_REGS];
+};
+
+static int da7280_parse_args(struct device *dev,
+ char *cmd, struct parse_data_t *ptn)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ char *tok; /* used to separate tokens */
+ const char ct[] = " \t"; /* space or tab delimits the tokens */
+ int tok_count = 0; /* total number of tokens parsed */
+ int i = 0, val;
+
+ ptn->len = 0;
+
+ /* parse the input string */
+ while ((tok = strsep(&cmd, ct)) != NULL) {
+ /* this is a value to be written to the register */
+ if (kstrtouint(tok, 0, &val) < 0) {
+ dev_err(haptics->dev,
+ "failed to read from %s\n", tok);
+ break;
+ }
+
+ if (i < MAX_PTN_REGS) {
+ ptn->val[i] = val;
+ i++;
+ }
+ tok_count++;
+ }
+
+ /* decide whether it is a read or write operation based on the
+ * value of tok_count and count_flag.
+ * tok_count = 0: no inputs, invalid case.
+ * tok_count = 1: write one value.
+ * tok_count > 1: write multiple values/patterns.
+ */
+ switch (tok_count) {
+ case 0:
+ return -EINVAL;
+ case 1:
+ ptn->len = 1;
+ break;
+ default:
+ ptn->len = i;
+ }
+ return 0;
+}
+
+static ssize_t
+patterns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ struct parse_data_t mem;
+ char cmd[MAX_USER_INPUT_LEN];
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(haptics->regmap, DA7280_MEM_CTL1, &val);
+ if (ret)
+ return ret;
+
+ if (count > MAX_USER_INPUT_LEN)
+ memcpy(cmd, buf, MAX_USER_INPUT_LEN);
+ else
+ memcpy(cmd, buf, count);
+ /* chop of '\n' introduced by echo at the end of the input */
+ if (cmd[count - 1] == '\n')
+ cmd[count - 1] = '\0';
+
+ if (da7280_parse_args(dev, cmd, &mem) < 0)
+ return -EINVAL;
+
+ memcpy(haptics->snp_mem, mem.val, mem.len);
+
+ ret = da7280_haptic_mem_update(haptics);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(ps_seq_id);
+static DEVICE_ATTR_RW(ps_seq_loop);
+static DEVICE_ATTR_RW(gpi_seq_id);
+static DEVICE_ATTR_RW(gpi_mode);
+static DEVICE_ATTR_RW(gpi_pol);
+static DEVICE_ATTR_WO(patterns);
+static struct attribute *da7280_sysfs_attr[] = {
+ &dev_attr_ps_seq_id.attr,
+ &dev_attr_ps_seq_loop.attr,
+ &dev_attr_gpi_seq_id.attr,
+ &dev_attr_gpi_mode.attr,
+ &dev_attr_gpi_pol.attr,
+ &dev_attr_patterns.attr,
+ NULL,
+};
+
+static const struct attribute_group da7280_attr_group = {
+ .attrs = da7280_sysfs_attr,
+};
+
+static const struct attribute_group *da7280_attr_groups[] = {
+ &da7280_attr_group,
+ NULL,
+};
+
+static int da7280_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct da7280_haptic *haptics;
+ unsigned int period2freq;
+ int ret;
+
+ haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+ haptics->dev = dev;
+
+ if (!client->irq) {
+ dev_err(dev, "No IRQ configured\n");
+ return -EINVAL;
+ }
+
+ /* Handle DT data if provided */
+ if (client->dev.of_node)
+ da7280_of_to_pdata(&client->dev, haptics);
+
+ if (haptics->op_mode == DA7280_PWM_MODE) {
+ /* Get pwm and regulatot for haptics device */
+ haptics->pwm_dev = devm_pwm_get(&client->dev, NULL);
+ if (IS_ERR(haptics->pwm_dev)) {
+ dev_err(dev, "failed to get PWM device\n");
+ return PTR_ERR(haptics->pwm_dev);
+ }
+
+ /*
+ * FIXME: pwm_apply_args() should be removed when switching to
+ * the atomic PWM API.
+ */
+ pwm_apply_args(haptics->pwm_dev);
+
+ /* Check PWM Period, it must be in 10k ~ 250kHz */
+ period2freq = 1000000 / pwm_get_period(haptics->pwm_dev);
+ if (period2freq < DA7280_MIN_PWM_FREQ_KHZ ||
+ period2freq > DA7280_MAX_PWM_FREQ_KHZ) {
+ dev_err(dev, "Not supported PWM frequency(%d)\n",
+ period2freq);
+ return -EINVAL;
+ }
+ }
+
+ INIT_WORK(&haptics->work, da7280_haptic_work);
+ haptics->client = client;
+ i2c_set_clientdata(client, haptics);
+
+ haptics->regmap =
+ devm_regmap_init_i2c(client, &da7280_haptic_regmap_config);
+ if (IS_ERR(haptics->regmap)) {
+ ret = PTR_ERR(haptics->regmap);
+ dev_err(dev, "Failed to allocate register map : %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ da7280_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "da7280-haptics", haptics);
+ if (ret != 0) {
+ dev_err(dev,
+ "Failed to request IRQ : %d\n", client->irq);
+ return ret;
+ }
+
+ ret = da7280_init(haptics);
+ if (ret) {
+ dev_err(dev, "failed to initialize device\n");
+ return ret;
+ }
+
+ /* Initialize input device for haptic device */
+ haptics->input_dev = devm_input_allocate_device(dev);
+ if (!haptics->input_dev) {
+ dev_err(dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ haptics->input_dev->name = "da7280-haptic";
+ haptics->input_dev->dev.parent = client->dev.parent;
+ haptics->input_dev->open = da7280_haptic_open;
+ haptics->input_dev->close = da7280_haptic_close;
+ input_set_drvdata(haptics->input_dev, haptics);
+ input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+ ret = input_ff_create_memless(haptics->input_dev, NULL,
+ da7280_haptic_play);
+ if (ret) {
+ dev_err(dev, "failed to create force-feedback\n");
+ return ret;
+ }
+
+#ifdef CONFIG_SYSFS
+ haptics->input_dev->dev.groups = da7280_attr_groups;
+#endif
+
+ ret = input_register_device(haptics->input_dev);
+ if (ret)
+ dev_err(dev, "failed to register input device\n");
+
+ return ret;
+}
+
+static int __maybe_unused da7280_suspend(struct device *dev)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+ if (haptics->suspend_state == 0) {
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK, 0);
+ if (ret)
+ dev_err(haptics->dev,
+ "I2C error : %d\n", ret);
+ else
+ haptics->suspend_state = 1;
+ }
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static int __maybe_unused da7280_resume(struct device *dev)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+ if (haptics->suspend_state) {
+ ret = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (ret)
+ dev_err(haptics->dev,
+ "i2c error : %d\n", ret);
+ else
+ haptics->suspend_state = 0;
+ }
+ mutex_unlock(&haptics->input_dev->mutex);
+ return ret;
+}
+
+static const struct of_device_id da7280_of_match[] = {
+ { .compatible = "dlg,da7280", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, da7280_of_match);
+
+static const struct i2c_device_id da7280_i2c_id[] = {
+ { "da7280", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, da7280_i2c_id);
+
+static SIMPLE_DEV_PM_OPS(da7280_pm_ops,
+ da7280_suspend, da7280_resume);
+
+static struct i2c_driver da7280_driver = {
+ .driver = {
+ .name = "da7280",
+ .of_match_table = of_match_ptr(da7280_of_match),
+ .pm = &da7280_pm_ops,
+ },
+ .probe = da7280_probe,
+ .id_table = da7280_i2c_id,
+};
+module_i2c_driver(da7280_driver);
+
+MODULE_DESCRIPTION("DA7280 haptics driver");
+MODULE_AUTHOR("Roy Im <Roy.Im.Opensource@diasemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/da7280.h b/drivers/input/misc/da7280.h
new file mode 100644
index 0000000..d9310b6
--- /dev/null
+++ b/drivers/input/misc/da7280.h
@@ -0,0 +1,412 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * DA7280 Haptic device driver registers
+ *
+ * Copyright (c) 2017 Dialog Semiconductor.
+ * Author: Roy Im <Roy.Im.Opensource@diasemi.com>
+ */
+
+#ifndef _DA7280_REG_DEFS_H
+#define _DA7280_REG_DEFS_H
+
+#include <linux/bitops.h>
+
+/* Registers */
+
+#define DA7280_CHIP_REV 0x00
+#define DA7280_IRQ_EVENT1 0x03
+#define DA7280_IRQ_EVENT_WARNING_DIAG 0x04
+#define DA7280_IRQ_EVENT_SEQ_DIAG 0x05
+#define DA7280_IRQ_STATUS1 0x06
+#define DA7280_IRQ_MASK1 0x07
+#define DA7280_CIF_I2C1 0x08
+#define DA7280_FRQ_LRA_PER_H 0x0A
+#define DA7280_FRQ_LRA_PER_L 0x0B
+#define DA7280_ACTUATOR1 0x0C
+#define DA7280_ACTUATOR2 0x0D
+#define DA7280_ACTUATOR3 0x0E
+#define DA7280_CALIB_V2I_H 0x0F
+#define DA7280_CALIB_V2I_L 0x10
+#define DA7280_CALIB_IMP_H 0x11
+#define DA7280_CALIB_IMP_L 0x12
+#define DA7280_TOP_CFG1 0x13
+#define DA7280_TOP_CFG2 0x14
+#define DA7280_TOP_CFG3 0x15
+#define DA7280_TOP_CFG4 0x16
+#define DA7280_TOP_INT_CFG1 0x17
+#define DA7280_TOP_INT_CFG6_H 0x1C
+#define DA7280_TOP_INT_CFG6_L 0x1D
+#define DA7280_TOP_INT_CFG7_H 0x1E
+#define DA7280_TOP_INT_CFG7_L 0x1F
+#define DA7280_TOP_INT_CFG8 0x20
+#define DA7280_TOP_CTL1 0x22
+#define DA7280_TOP_CTL2 0x23
+#define DA7280_SEQ_CTL1 0x24
+#define DA7280_SWG_C1 0x25
+#define DA7280_SWG_C2 0x26
+#define DA7280_SWG_C3 0x27
+#define DA7280_SEQ_CTL2 0x28
+#define DA7280_GPI_0_CTL 0x29
+#define DA7280_GPI_1_CTL 0x2A
+#define DA7280_GPI_2_CTL 0x2B
+#define DA7280_MEM_CTL1 0x2C
+#define DA7280_MEM_CTL2 0x2D
+#define DA7280_ADC_DATA_H1 0x2E
+#define DA7280_ADC_DATA_L1 0x2F
+#define DA7280_POLARITY 0x43
+#define DA7280_LRA_AVR_H 0x44
+#define DA7280_LRA_AVR_L 0x45
+#define DA7280_FRQ_LRA_PER_ACT_H 0x46
+#define DA7280_FRQ_LRA_PER_ACT_L 0x47
+#define DA7280_FRQ_PHASE_H 0x48
+#define DA7280_FRQ_PHASE_L 0x49
+#define DA7280_FRQ_CTL 0x4C
+#define DA7280_TRIM3 0x5F
+#define DA7280_TRIM4 0x60
+#define DA7280_TRIM6 0x62
+#define DA7280_TOP_CFG5 0x6E
+#define DA7280_IRQ_EVENT_ACTUATOR_FAULT 0x81
+#define DA7280_IRQ_STATUS2 0x82
+#define DA7280_IRQ_MASK2 0x83
+#define DA7280_SNP_MEM_0 0x84
+#define DA7280_SNP_MEM_99 0xE7
+
+/* DA7280_CHIP_REV (Address 0x00) */
+#define DA7280_CHIP_REV_MAJOR_SHIFT 0
+#define DA7280_CHIP_REV_MAJOR_MASK (15 << 0)
+#define DA7280_CHIP_REV_MINOR_SHIFT 4
+#define DA7280_CHIP_REV_MINOR_MASK (15 << 4)
+
+/* DA7280_IRQ_EVENT1 (Address 0x03) */
+#define DA7280_E_SEQ_CONTINUE_SHIFT 0
+#define DA7280_E_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_E_UVLO_SHIFT 1
+#define DA7280_E_UVLO_MASK BIT(1)
+#define DA7280_E_SEQ_DONE_SHIFT 2
+#define DA7280_E_SEQ_DONE_MASK BIT(2)
+#define DA7280_E_OVERTEMP_CRIT_SHIFT 3
+#define DA7280_E_OVERTEMP_CRIT_MASK BIT(3)
+#define DA7280_E_SEQ_FAULT_SHIFT 4
+#define DA7280_E_SEQ_FAULT_MASK BIT(4)
+#define DA7280_E_WARNING_SHIFT 5
+#define DA7280_E_WARNING_MASK BIT(5)
+#define DA7280_E_ACTUATOR_FAULT_SHIFT 6
+#define DA7280_E_ACTUATOR_FAULT_MASK BIT(6)
+#define DA7280_E_OC_FAULT_SHIFT 7
+#define DA7280_E_OC_FAULT_MASK BIT(7)
+
+/* DA7280_IRQ_EVENT_WARNING_DIAG (Address 0x04) */
+#define DA7280_E_OVERTEMP_WARN_SHIFT 3
+#define DA7280_E_OVERTEMP_WARN_MASK BIT(3)
+#define DA7280_E_MEM_TYPE_SHIFT 4
+#define DA7280_E_MEM_TYPE_MASK BIT(4)
+#define DA7280_E_LIM_DRIVE_ACC_SHIFT 6
+#define DA7280_E_LIM_DRIVE_ACC_MASK BIT(6)
+#define DA7280_E_LIM_DRIVE_SHIFT 7
+#define DA7280_E_LIM_DRIVE_MASK BIT(7)
+
+/* DA7280_IRQ_EVENT_PAT_DIAG (Address 0x05) */
+#define DA7280_E_PWM_FAULT_SHIFT 5
+#define DA7280_E_PWM_FAULT_MASK BIT(5)
+#define DA7280_E_MEM_FAULT_SHIFT 6
+#define DA7280_E_MEM_FAULT_MASK BIT(6)
+#define DA7280_E_SEQ_ID_FAULT_SHIFT 7
+#define DA7280_E_SEQ_ID_FAULT_MASK BIT(7)
+
+/* DA7280_IRQ_STATUS1 (Address 0x06) */
+#define DA7280_STA_SEQ_CONTINUE_SHIFT 0
+#define DA7280_STA_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_STA_UVLO_VBAT_OK_SHIFT 1
+#define DA7280_STA_UVLO_VBAT_OK_MASK BIT(1)
+#define DA7280_STA_SEQ_DONE_SHIFT 2
+#define DA7280_STA_SEQ_DONE_MASK BIT(2)
+#define DA7280_STA_OVERTEMP_CRIT_SHIFT 3
+#define DA7280_STA_OVERTEMP_CRIT_MASK BIT(3)
+#define DA7280_STA_SEQ_FAULT_SHIFT 4
+#define DA7280_STA_SEQ_FAULT_MASK BIT(4)
+#define DA7280_STA_WARNING_SHIFT 5
+#define DA7280_STA_WARNING_MASK BIT(5)
+#define DA7280_STA_ACTUATOR_SHIFT 6
+#define DA7280_STA_ACTUATOR_MASK BIT(6)
+#define DA7280_STA_OC_SHIFT 7
+#define DA7280_STA_OC_MASK BIT(7)
+
+/* DA7280_IRQ_MASK1 (Address 0x07) */
+#define DA7280_SEQ_CONTINUE_M_SHIFT 0
+#define DA7280_SEQ_CONTINUE_M_MASK BIT(0)
+#define DA7280_E_UVLO_M_SHIFT 1
+#define DA7280_E_UVLO_M_MASK BIT(1)
+#define DA7280_SEQ_DONE_M_SHIFT 2
+#define DA7280_SEQ_DONE_M_MASK BIT(2)
+#define DA7280_OVERTEMP_CRIT_M_SHIFT 3
+#define DA7280_OVERTEMP_CRIT_M_MASK BIT(3)
+#define DA7280_SEQ_FAULT_M_SHIFT 4
+#define DA7280_SEQ_FAULT_M_MASK BIT(4)
+#define DA7280_WARNING_M_SHIFT 5
+#define DA7280_WARNING_M_MASK BIT(5)
+#define DA7280_ACTUATOR_M_SHIFT 6
+#define DA7280_ACTUATOR_M_MASK BIT(6)
+#define DA7280_OC_M_SHIFT 7
+#define DA7280_OC_M_MASK BIT(7)
+
+/* DA7280_CIF_I2C1 (Address 0x08) */
+#define DA7280_I2C_TO_ENABLE_SHIFT 6
+#define DA7280_I2C_TO_ENABLE_MASK BIT(6)
+#define DA7280_I2C_WR_MODE_SHIFT 7
+#define DA7280_I2C_WR_MODE_MASK BIT(7)
+
+/* DA7280_FRQ_LRA_PER_H (Address 0x0a) */
+#define DA7280_LRA_PER_H_SHIFT 0
+#define DA7280_LRA_PER_H_MASK (255 << 0)
+
+/* DA7280_FRQ_LRA_PER_L (Address 0x0b) */
+#define DA7280_LRA_PER_L_SHIFT 0
+#define DA7280_LRA_PER_L_MASK (127 << 0)
+
+/* DA7280_ACTUATOR1 (Address 0x0c) */
+#define DA7280_ACTUATOR_NOMMAX_SHIFT 0
+#define DA7280_ACTUATOR_NOMMAX_MASK (255 << 0)
+
+/* DA7280_ACTUATOR2 (Address 0x0d) */
+#define DA7280_ACTUATOR_ABSMAX_SHIFT 0
+#define DA7280_ACTUATOR_ABSMAX_MASK (255 << 0)
+
+/* DA7280_ACTUATOR3 (Address 0x0e) */
+#define DA7280_IMAX_SHIFT 0
+#define DA7280_IMAX_MASK (31 << 0)
+
+/* DA7280_CALIB_V2I_H (Address 0x0f) */
+#define DA7280_V2I_FACTOR_H_SHIFT 0
+#define DA7280_V2I_FACTOR_H_MASK (255 << 0)
+
+/* DA7280_CALIB_V2I_L (Address 0x10) */
+#define DA7280_V2I_FACTOR_L_SHIFT 0
+#define DA7280_V2I_FACTOR_L_MASK (255 << 0)
+
+/* DA7280_CALIB_IMP_H (Address 0x11) */
+#define DA7280_IMPEDANCE_H_SHIFT 0
+#define DA7280_IMPEDANCE_H_MASK (255 << 0)
+
+/* DA7280_CALIB_IMP_L (Address 0x12) */
+#define DA7280_IMPEDANCE_L_SHIFT 0
+#define DA7280_IMPEDANCE_L_MASK (3 << 0)
+
+/* DA7280_TOP_CFG1 (Address 0x13) */
+#define DA7280_AMP_PID_EN_SHIFT 0
+#define DA7280_AMP_PID_EN_MASK BIT(0)
+#define DA7280_RAPID_STOP_EN_SHIFT 1
+#define DA7280_RAPID_STOP_EN_MASK BIT(1)
+#define DA7280_ACCELERATION_EN_SHIFT 2
+#define DA7280_ACCELERATION_EN_MASK BIT(2)
+#define DA7280_FREQ_TRACK_EN_SHIFT 3
+#define DA7280_FREQ_TRACK_EN_MASK BIT(3)
+#define DA7280_BEMF_SENSE_EN_SHIFT 4
+#define DA7280_BEMF_SENSE_EN_MASK BIT(4)
+#define DA7280_ACTUATOR_TYPE_SHIFT 5
+#define DA7280_ACTUATOR_TYPE_MASK BIT(5)
+#define DA7280_EMBEDDED_MODE_SHIFT 7
+#define DA7280_EMBEDDED_MODE_MASK BIT(7)
+
+/* DA7280_TOP_CFG2 (Address 0x14) */
+#define DA7280_FULL_BRAKE_THR_SHIFT 0
+#define DA7280_FULL_BRAKE_THR_MASK (15 << 0)
+#define DA7280_MEM_DATA_SIGNED_SHIFT 4
+#define DA7280_MEM_DATA_SIGNED_MASK BIT(4)
+
+/* DA7280_TOP_CFG3 (Address 0x15) */
+#define DA7280_VDD_MARGIN_SHIFT 0
+#define DA7280_VDD_MARGIN_MASK (15 << 0)
+
+/* DA7280_TOP_CFG4 (Address 0x16) */
+#define DA7280_TST_CALIB_IMPEDANCE_DIS_SHIFT 6
+#define DA7280_TST_CALIB_IMPEDANCE_DIS_MASK BIT(6)
+#define DA7280_V2I_FACTOR_FREEZE_SHIFT 7
+#define DA7280_V2I_FACTOR_FREEZE_MASK BIT(7)
+
+/* DA7280_TOP_INT_CFG1 (Address 0x17) */
+#define DA7280_BEMF_FAULT_LIM_SHIFT 0
+#define DA7280_BEMF_FAULT_LIM_MASK (3 << 0)
+#define DA7280_FRQ_LOCKED_LIM_SHIFT 2
+#define DA7280_FRQ_LOCKED_LIM_MASK (63 << 2)
+
+/* DA7280_TOP_INT_CFG6_H (Address 0x1c) */
+#define DA7280_FRQ_PID_KP_H_SHIFT 0
+#define DA7280_FRQ_PID_KP_H_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG6_L (Address 0x1d) */
+#define DA7280_FRQ_PID_KP_L_SHIFT 0
+#define DA7280_FRQ_PID_KP_L_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG7_H (Address 0x1e) */
+#define DA7280_FRQ_PID_KI_H_SHIFT 0
+#define DA7280_FRQ_PID_KI_H_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG7_L (Address 0x1f) */
+#define DA7280_FRQ_PID_KI_L_SHIFT 0
+#define DA7280_FRQ_PID_KI_L_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG8 (Address 0x20) */
+#define DA7280_TST_FRQ_TRACK_BEMF_LIM_SHIFT 0
+#define DA7280_TST_FRQ_TRACK_BEMF_LIM_MASK (15 << 0)
+#define DA7280_TST_AMP_RAPID_STOP_LIM_SHIFT 4
+#define DA7280_TST_AMP_RAPID_STOP_LIM_MASK (7 << 4)
+
+/* DA7280_TOP_CTL1 (Address 0x22) */
+#define DA7280_OPERATION_MODE_SHIFT 0
+#define DA7280_OPERATION_MODE_MASK (7 << 0)
+#define DA7280_STANDBY_EN_SHIFT 3
+#define DA7280_STANDBY_EN_MASK BIT(3)
+#define DA7280_SEQ_START_SHIFT 4
+#define DA7280_SEQ_START_MASK BIT(4)
+
+/* DA7280_TOP_CTL2 (Address 0x23) */
+#define DA7280_OVERRIDE_VAL_SHIFT 0
+#define DA7280_OVERRIDE_VAL_MASK (255 << 0)
+
+/* DA7280_SEQ_CTL1 (Address 0x24) */
+#define DA7280_SEQ_CONTINUE_SHIFT 0
+#define DA7280_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_WAVEGEN_MODE_SHIFT 1
+#define DA7280_WAVEGEN_MODE_MASK BIT(1)
+#define DA7280_FREQ_WAVEFORM_TIMEBASE_SHIFT 2
+#define DA7280_FREQ_WAVEFORM_TIMEBASE_MASK BIT(2)
+
+/* DA7280_SWG_C1 (Address 0x25) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF1_SHIFT 0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF1_MASK (255 << 0)
+
+/* DA7280_SWG_C2 (Address 0x26) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF2_SHIFT 0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF2_MASK (255 << 0)
+
+/* DA7280_SWG_C3 (Address 0x27) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF3_SHIFT 0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF3_MASK (255 << 0)
+
+/* DA7280_SEQ_CTL2 (Address 0x28) */
+#define DA7280_PS_SEQ_ID_SHIFT 0
+#define DA7280_PS_SEQ_ID_MASK (15 << 0)
+#define DA7280_PS_SEQ_LOOP_SHIFT 4
+#define DA7280_PS_SEQ_LOOP_MASK (15 << 4)
+
+/* DA7280_GPIO_0_CTL (Address 0x29) */
+#define DA7280_GPI0_POLARITY_SHIFT 0
+#define DA7280_GPI0_POLARITY_MASK (3 << 0)
+#define DA7280_GPI0_MODE_SHIFT 2
+#define DA7280_GPI0_MODE_MASK BIT(2)
+#define DA7280_GPI0_SEQUENCE_ID_SHIFT 3
+#define DA7280_GPI0_SEQUENCE_ID_MASK (15 << 3)
+
+/* DA7280_GPIO_1_CTL (Address 0x2a) */
+#define DA7280_GPI1_POLARITY_SHIFT 0
+#define DA7280_GPI1_POLARITY_MASK (3 << 0)
+#define DA7280_GPI1_MODE_SHIFT 2
+#define DA7280_GPI1_MODE_MASK BIT(2)
+#define DA7280_GPI1_SEQUENCE_ID_SHIFT 3
+#define DA7280_GPI1_SEQUENCE_ID_MASK (15 << 3)
+
+/* DA7280_GPIO_2_CTL (Address 0x2b) */
+#define DA7280_GPI2_POLARITY_SHIFT 0
+#define DA7280_GPI2_POLARITY_MASK (3 << 0)
+#define DA7280_GPI2_MODE_SHIFT 2
+#define DA7280_GPI2_MODE_MASK BIT(2)
+#define DA7280_GPI2_SEQUENCE_ID_SHIFT 3
+#define DA7280_GPI2_SEQUENCE_ID_MASK (15 << 3)
+
+/* DA7280_MEM_CTL1 (Address 0x2c) */
+#define DA7280_WAV_MEM_BASE_ADDR_SHIFT 0
+#define DA7280_WAV_MEM_BASE_ADDR_MASK (255 << 0)
+
+/* DA7280_MEM_CTL2 (Address 0x2d) */
+#define DA7280_WAV_MEM_LOCK_SHIFT 7
+#define DA7280_WAV_MEM_LOCK_MASK BIT(7)
+
+/* DA7280_ADC_DATA_H1 (Address 0x2e) */
+#define DA7280_ADC_VDD_H_SHIFT 0
+#define DA7280_ADC_VDD_H_MASK (255 << 0)
+
+/* DA7280_ADC_DATA_L1 (Address 0x2f) */
+#define DA7280_ADC_VDD_L_SHIFT 0
+#define DA7280_ADC_VDD_L_MASK (127 << 0)
+
+/* DA7280_POLARITY (Address 0x43) */
+#define DA7280_POLARITY_SHIFT 0
+#define DA7280_POLARITY_MASK BIT(0)
+
+/* DA7280_LRA_AVR_H (Address 0x44) */
+#define DA7280_LRA_PER_AVERAGE_H_SHIFT 0
+#define DA7280_LRA_PER_AVERAGE_H_MASK (255 << 0)
+
+/* DA7280_LRA_AVR_L (Address 0x45) */
+#define DA7280_LRA_PER_AVERAGE_L_SHIFT 0
+#define DA7280_LRA_PER_AVERAGE_L_MASK (127 << 0)
+
+/* DA7280_FRQ_LRA_PER_ACT_H (Address 0x46) */
+#define DA7280_LRA_PER_ACTUAL_H_SHIFT 0
+#define DA7280_LRA_PER_ACTUAL_H_MASK (255 << 0)
+
+/* DA7280_FRQ_LRA_PER_ACT_L (Address 0x47) */
+#define DA7280_LRA_PER_ACTUAL_L_SHIFT 0
+#define DA7280_LRA_PER_ACTUAL_L_MASK (127 << 0)
+
+/* DA7280_FRQ_PHASE_H (Address 0x48) */
+#define DA7280_PHASE_DELAY_H_SHIFT 0
+#define DA7280_PHASE_DELAY_H_MASK (255 << 0)
+
+/* DA7280_FRQ_PHASE_L (Address 0x49) */
+#define DA7280_DELAY_SHIFT_L_SHIFT 0
+#define DA7280_DELAY_SHIFT_L_MASK (7 << 0)
+#define DA7280_DELAY_SHIFT_FREEZE_SHIFT 7
+#define DA7280_DELAY_SHIFT_FREEZE_MASK BIT(7)
+
+/* DA7280_FRQ_CTL (Address 0x4c) */
+#define DA7280_FREQ_TRACKING_FORCE_ON_SHIFT 0
+#define DA7280_FREQ_TRACKING_FORCE_ON_MASK BIT(0)
+#define DA7280_FREQ_TRACKING_AUTO_ADJ_SHIFT 1
+#define DA7280_FREQ_TRACKING_AUTO_ADJ_MASK BIT(1)
+
+/* DA7280_TRIM3 (Address 0x5f) */
+#define DA7280_REF_UVLO_THRES_SHIFT 3
+#define DA7280_REF_UVLO_THRES_MASK (3 << 3)
+#define DA7280_LOOP_FILT_LOW_BW_SHIFT 5
+#define DA7280_LOOP_FILT_LOW_BW_MASK BIT(5)
+#define DA7280_LOOP_IDAC_DOUBLE_RANGE_SHIFT 6
+#define DA7280_LOOP_IDAC_DOUBLE_RANGE_MASK BIT(6)
+
+/* DA7280_TRIM4 (Address 0x60) */
+#define DA7280_LOOP_FILT_RES_TRIM_SHIFT 0
+#define DA7280_LOOP_FILT_RES_TRIM_MASK (3 << 0)
+#define DA7280_LOOP_FILT_CAP_TRIM_SHIFT 2
+#define DA7280_LOOP_FILT_CAP_TRIM_MASK (3 << 2)
+
+/* DA7280_TRIM6 (Address 0x62) */
+#define DA7280_HBRIDGE_ERC_HS_TRIM_SHIFT 0
+#define DA7280_HBRIDGE_ERC_HS_TRIM_MASK (3 << 0)
+#define DA7280_HBRIDGE_ERC_LS_TRIM_SHIFT 2
+#define DA7280_HBRIDGE_ERC_LS_TRIM_MASK (3 << 2)
+
+/* DA7280_TOP_CFG5 (Address 0x6e) */
+#define DA7280_V2I_FACTOR_OFFSET_EN_SHIFT 0
+#define DA7280_V2I_FACTOR_OFFSET_EN_MASK BIT(0)
+#define DA7280_FRQ_PAUSE_ON_POLARITY_CHANGE_SHIFT 1
+#define DA7280_FRQ_PAUSE_ON_POLARITY_CHANGE_MASK BIT(1)
+#define DA7280_DELAY_BYPASS_SHIFT 2
+#define DA7280_DELAY_BYPASS_MASK BIT(2)
+
+/* DA7280_IRQ_EVENT_ACTUATOR_FAULT (Address 0x81) */
+#define DA7280_ADC_SAT_FAULT_SHIFT 2
+#define DA7280_ADC_SAT_FAULT_MASK BIT(2)
+
+/* DA7280_IRQ_STATUS2 (Address 0x82) */
+#define DA7280_STA_ADC_SAT_SHIFT 7
+#define DA7280_STA_ADC_SAT_MASK BIT(7)
+
+/* DA7280_IRQ_MASK2 (Address 0x83) */
+#define DA7280_ADC_SAT_M_SHIFT 7
+#define DA7280_ADC_SAT_M_MASK BIT(7)
+
+/* DA7280_SNP_MEM_XX (Address 0x84 ~ 0xe7) */
+#define DA7280_SNP_MEM_SHIFT 0
+#define DA7280_SNP_MEM_MASK (255 << 0)
+
+#endif
--
end-of-patch for PATCH V6
^ permalink raw reply related
* [PATCH V6 2/3] dt-bindings: input: Add document bindings for DA7280
From: Roy Im @ 2018-09-11 12:18 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring
Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
linux-kernel
In-Reply-To: <cover.1536668286.git.Roy.Im@diasemi.com>
Add device tree binding information for DA7280 haptic driver.
Example bindings for DA7280 are added.
Reviewed-by: Rob Herring <robh@kernel.org>.
Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>
---
v6: No changes.
v5: Updated descriptions and fixed errors.
v4: Fixed commit message, properties.
v3: Fixed subject format.
v2: No changes
.../devicetree/bindings/input/dlg,da7280.txt | 105 ++++++++++++++++++++
1 file changed, 105 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/dlg,da7280.txt
diff --git a/Documentation/devicetree/bindings/input/dlg,da7280.txt b/Documentation/devicetree/bindings/input/dlg,da7280.txt
new file mode 100644
index 0000000..a25a12f
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/dlg,da7280.txt
@@ -0,0 +1,105 @@
+Dialog Semiconductor DA7280 Haptics bindings
+
+Required properties:
+- compatible: Should be "dlg,da7280".
+- reg: Specifies the I2C slave address.
+
+- interrupt-parent : Specifies the phandle of the interrupt controller to
+ which the IRQs from DA7280 are delivered to.
+
+- dlg,actuator-type: Set Actuator type. it should be one of:
+ "LRA" - Linear Resonance Actuator type.
+ "ERM-bar" - Bar type Eccentric Rotating Mass.
+ "ERM-coin" - Coin type Eccentric Rotating Mass.
+
+- dlg,op-mode: Haptic operation mode.
+ Possible values:
+ 1 - Direct register override(DRO) mode triggered by i2c(default),
+ 2 - PWM data source mode controlled by PWM duty,
+ 3 - Register triggered waveform memory(RTWM) mode, the pattern
+ assigned to the PS_SEQ_ID played as much times as PS_SEQ_LOOP,
+ 4 - Edge triggered waveform memory(ETWM) mode, external GPI(N)
+ control are required to enable/disable and it needs to keep
+ device enabled by sending magnitude (X > 0),
+ the pattern is assigned to the GPI(N)_SEQUENCE_ID below.
+ For more details, please see the datasheet.
+
+- dlg,nom-microvolt: Nominal actuator voltage rating.
+ Valid values: 0 - 6000000.
+- dlg,abs-max-microvolt: Absolute actuator maximum voltage rating.
+ Valid values: 0 - 6000000.
+- dlg,imax-microamp: Actuator max current rating.
+ Valid values: 0 - 252000.
+ Default: 130000.
+- dlg,impd-micro-ohms: the impedance of the actuator in micro ohms.
+ Valid values: 0 - 1500000000.
+
+Optional properties:
+- pwms : phandle to the physical PWM(Pulse Width Modulation) device.
+ PWM properties should be named "pwms". And number of cell is different
+ for each pwm device.
+ (See Documentation/devicetree/bindings/pwm/pwm.txt
+ for further information relating to pwm properties)
+
+- dlg,ps-seq-id: the PS_SEQ_ID(pattern ID in waveform memory inside chip)
+ to play back when RTWM-MODE is enabled.
+ Valid range: 0 - 15.
+- dlg,ps-seq-loop: the PS_SEQ_LOOP, Number of times the pre-stored sequence
+ pointed to by PS_SEQ_ID or GPI(N)_SEQUENCE_ID is repeated.
+ Valid range: 0 - 15.
+- dlg,gpiN-seq-id: the GPI(N)_SEQUENCE_ID, pattern to play
+ when gpi0 is triggered, 'N' must be 0 - 2.
+ Valid range: 0 - 15.
+- dlg,gpiN-mode: the pattern mode which can select either
+ "Single-pattern" or "Multi-pattern", 'N' must be 0 - 2.
+- dlg,gpiN-polarity: gpiN polarity which can be chosen among
+ "Rising-edge", "Falling-edge" and "Both-edge",
+ 'N' must be 0 - 2
+ Haptic will work by this edge option in case of ETWM mode.
+
+- dlg,resonant-freq-hz: use in case of LRA.
+ the frequency range: 50 - 300.
+ Default: 205.
+
+- dlg,bemf-sens-enable: Enable for internal loop computations.
+- dlg,freq-track-enable: Enable for resonant frequency tracking.
+- dlg,acc-enable: Enable for active acceleration.
+- dlg,rapid-stop-enable: Enable for rapid stop.
+- dlg,amp-pid-enable: Enable for the amplitude PID.
+- dlg,mem-array: Customized waveform memory(patterns) data downloaded to
+ the device during initialization. This is an array of 100 values(u8).
+
+For further information, see device datasheet.
+
+======
+
+Example:
+
+ haptics: da7280-haptics@4a {
+ compatible = "dlg,da7280";
+ reg = <0x4a>;
+ interrupt-parent = <&gpio6>;
+ interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
+ dlg,actuator-type = "LRA";
+ dlg,op-mode = <1>;
+ dlg,nom-microvolt = <2000000>;
+ dlg,abs-max-microvolt = <2000000>;
+ dlg,imax-microamp = <170000>;
+ dlg,resonant-freq-hz = <180>;
+ dlg,impd-micro-ohms = <10500000>;
+ dlg,freq-track-enable;
+ dlg,rapid-stop-enable;
+ dlg,mem-array = <
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ >;
+
+ };
--
end-of-patch for PATCH V6
^ permalink raw reply related
* [PATCH V6 1/3] MAINTAINERS: da7280 updates to the Dialog Semiconductor search terms
From: Roy Im @ 2018-09-11 12:18 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring
Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
linux-kernel
In-Reply-To: <cover.1536668286.git.Roy.Im@diasemi.com>
This patch adds the da7280 bindings doc and driver to the Dialog
Semiconductor support list.
Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>
---
v6: No changes.
v5: No changes.
v4: No changes.
v3: No changes.
v2: No changes.
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d870cb5..6244a7d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4313,6 +4313,7 @@ S: Supported
F: Documentation/hwmon/da90??
F: Documentation/devicetree/bindings/mfd/da90*.txt
F: Documentation/devicetree/bindings/input/da90??-onkey.txt
+F: Documentation/devicetree/bindings/input/dlg,da72??.txt
F: Documentation/devicetree/bindings/thermal/da90??-thermal.txt
F: Documentation/devicetree/bindings/regulator/da92*.txt
F: Documentation/devicetree/bindings/watchdog/da90??-wdt.txt
@@ -4321,6 +4322,7 @@ F: drivers/gpio/gpio-da90??.c
F: drivers/hwmon/da90??-hwmon.c
F: drivers/iio/adc/da91??-*.c
F: drivers/input/misc/da90??_onkey.c
+F: drivers/input/misc/da72??.[ch]
F: drivers/input/touchscreen/da9052_tsi.c
F: drivers/leds/leds-da90??.c
F: drivers/mfd/da903x.c
--
end-of-patch for PATCH V6
^ permalink raw reply related
* [PATCH V6 0/3] da7280: haptic driver submission
From: Roy Im @ 2018-09-11 12:18 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring
Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
linux-kernel
This patch adds support for the Dialog DA7280 Haptic driver IC.
In this patch set the following is provided:
[PATCH V2 1/3] MAINTAINERS file update for DA7280
[PATCH V2 2/3] DA7280 DT Binding
[PATCH V2 3/3] DA7280 Driver
This patch applies against linux-next and v4.19-rc3
Thank you,
Roy Im, Dialog Semiconductor Ltd.
Roy Im (3):
MAINTAINERS: da7280 updates to the Dialog Semiconductor search terms
dt-bindings: input: Add document bindings for DA7280
Input: new da7280 haptic driver
.../devicetree/bindings/input/dlg,da7280.txt | 105 ++
MAINTAINERS | 2 +
drivers/input/misc/Kconfig | 13 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/da7280.c | 1438 ++++++++++++++++++++
drivers/input/misc/da7280.h | 412 ++++++
6 files changed, 1971 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/dlg,da7280.txt
create mode 100644 drivers/input/misc/da7280.c
create mode 100644 drivers/input/misc/da7280.h
--
end-of-patch for PATCH V6
^ permalink raw reply
* Re: mfd: cros_ec: Check for mkbp events on resume only if supported.
From: Lee Jones @ 2018-09-10 14:37 UTC (permalink / raw)
To: RaviChandra Sadineni
Cc: dmitry.torokhov, ravisadineni, dtor, linux-samsung-soc, tbroch,
linux-kernel, linux-input, tfiga, m.szyprowski
In-Reply-To: <20180820153419.32098-1-ravisadineni@google.com>
On Mon, 20 Aug 2018, RaviChandra Sadineni wrote:
> Currently on every resume we check for mkbp events and notify the
> clients. This helps in identifying the wakeup sources. But on devices
> that do not support mkbp protocol, we might end up querying key state of
> the keyboard in a loop which blocks the resume. Instead check for events
> only if mkbp is supported.
>
> Signed-off-by: RaviChandra Sadineni <ravisadineni@chromium.org>
> ---
>
> Note: This patch fixes the suspend/resume issue on Snow and Peach-Pit
> Chromebooks, both on vanilla v4.18 as well as linux-next from 20 August
> 2018. Further info at: https://lkml.org/lkml/2018/6/5/1076
>
> drivers/mfd/cros_ec.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
Applied, thanks.
--
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* [PATCH 8/8] Input: olpc_apsp: allocate the GPIOs used
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
Take the GPIO lines are used by the SP. The driver doesn't touch the
lines -- this is done to disallow anything else from fiddling with
them because that would confuse the SP firmware.
Also, the lines are now nicely visible in /sys/kernel/debug/gpio.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
drivers/input/serio/olpc_apsp.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/input/serio/olpc_apsp.c b/drivers/input/serio/olpc_apsp.c
index fe9e19014e70..287759c5f36d 100644
--- a/drivers/input/serio/olpc_apsp.c
+++ b/drivers/input/serio/olpc_apsp.c
@@ -24,6 +24,7 @@
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
/*
* The OLPC XO-1.75 and XO-4 laptops do not have a hardware PS/2 controller.
@@ -76,6 +77,8 @@ struct olpc_apsp {
struct serio *padio;
void __iomem *base;
struct clk *clk;
+ struct gpio_desc *clk_gpio;
+ struct gpio_desc *data_gpio;
int open_count;
int irq;
};
@@ -211,6 +214,16 @@ static int olpc_apsp_probe(struct platform_device *pdev)
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
+ priv->clk_gpio = devm_gpiod_get_optional(&pdev->dev, "clk",
+ GPIOD_ASIS);
+ if (IS_ERR(priv->clk_gpio))
+ return PTR_ERR(priv->clk_gpio);
+
+ priv->data_gpio = devm_gpiod_get_optional(&pdev->dev, "data",
+ GPIOD_ASIS);
+ if (IS_ERR(priv->data_gpio))
+ return PTR_ERR(priv->data_gpio);
+
/* KEYBOARD */
kb_serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
if (!kb_serio)
--
2.17.1
^ permalink raw reply related
* [PATCH 7/8] Input: olpc_apsp: enable the SP clock
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
Without the clock, the keyboard controller won't operate.
Tested on an OLPC XO 1.75.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
drivers/input/serio/olpc_apsp.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/input/serio/olpc_apsp.c b/drivers/input/serio/olpc_apsp.c
index 8b19a47dfa46..fe9e19014e70 100644
--- a/drivers/input/serio/olpc_apsp.c
+++ b/drivers/input/serio/olpc_apsp.c
@@ -23,6 +23,7 @@
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/delay.h>
+#include <linux/clk.h>
/*
* The OLPC XO-1.75 and XO-4 laptops do not have a hardware PS/2 controller.
@@ -74,6 +75,7 @@ struct olpc_apsp {
struct serio *kbio;
struct serio *padio;
void __iomem *base;
+ struct clk *clk;
int open_count;
int irq;
};
@@ -146,8 +148,13 @@ static int olpc_apsp_open(struct serio *port)
struct olpc_apsp *priv = port->port_data;
unsigned int tmp;
unsigned long l;
+ int error;
if (priv->open_count++ == 0) {
+ error = clk_prepare_enable(priv->clk);
+ if (error)
+ return error;
+
l = readl(priv->base + COMMAND_FIFO_STATUS);
if (!(l & CMD_STS_MASK)) {
dev_err(priv->dev, "SP cannot accept commands.\n");
@@ -171,6 +178,8 @@ static void olpc_apsp_close(struct serio *port)
/* Disable interrupt 0 */
tmp = readl(priv->base + PJ_INTERRUPT_MASK);
writel(tmp | INT_0, priv->base + PJ_INTERRUPT_MASK);
+
+ clk_disable_unprepare(priv->clk);
}
}
@@ -198,6 +207,10 @@ static int olpc_apsp_probe(struct platform_device *pdev)
if (priv->irq < 0)
return priv->irq;
+ priv->clk = devm_clk_get(&pdev->dev, "sp");
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
/* KEYBOARD */
kb_serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
if (!kb_serio)
--
2.17.1
^ permalink raw reply related
* [PATCH 6/8] Input: olpc_apsp: check FIFO status on open(), not probe()
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
Let's defer the FIFO status checking until open().
When we'll get a clk handle, this will allow us to defer clock enablement
until the device is actually used.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
drivers/input/serio/olpc_apsp.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/input/serio/olpc_apsp.c b/drivers/input/serio/olpc_apsp.c
index 8e9a4209fcad..8b19a47dfa46 100644
--- a/drivers/input/serio/olpc_apsp.c
+++ b/drivers/input/serio/olpc_apsp.c
@@ -145,8 +145,15 @@ static int olpc_apsp_open(struct serio *port)
{
struct olpc_apsp *priv = port->port_data;
unsigned int tmp;
+ unsigned long l;
if (priv->open_count++ == 0) {
+ l = readl(priv->base + COMMAND_FIFO_STATUS);
+ if (!(l & CMD_STS_MASK)) {
+ dev_err(priv->dev, "SP cannot accept commands.\n");
+ return -EIO;
+ }
+
/* Enable interrupt 0 by clearing its bit */
tmp = readl(priv->base + PJ_INTERRUPT_MASK);
writel(tmp & ~INT_0, priv->base + PJ_INTERRUPT_MASK);
@@ -173,7 +180,6 @@ static int olpc_apsp_probe(struct platform_device *pdev)
struct olpc_apsp *priv;
struct resource *res;
struct device_node *np;
- unsigned long l;
int error;
priv = devm_kzalloc(&pdev->dev, sizeof(struct olpc_apsp), GFP_KERNEL);
@@ -192,12 +198,6 @@ static int olpc_apsp_probe(struct platform_device *pdev)
if (priv->irq < 0)
return priv->irq;
- l = readl(priv->base + COMMAND_FIFO_STATUS);
- if (!(l & CMD_STS_MASK)) {
- dev_err(&pdev->dev, "SP cannot accept commands.\n");
- return -EIO;
- }
-
/* KEYBOARD */
kb_serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
if (!kb_serio)
--
2.17.1
^ permalink raw reply related
* [PATCH 5/8] Input: olpc_apsp: depend on CONFIG_OF
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
This is a device-tree enabled driver. Moreover CONFIG_OLPC is specific
to the x86 platform code, while the driver is for an ARM-based laptop.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
drivers/input/serio/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index d90d9f1098ff..a4f70021b53e 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -254,7 +254,7 @@ config SERIO_APBPS2
config SERIO_OLPC_APSP
tristate "OLPC AP-SP input support"
- depends on OLPC || COMPILE_TEST
+ depends on OF
help
Say Y here if you want support for the keyboard and touchpad included
in the OLPC XO-1.75 and XO-4 laptops.
--
2.17.1
^ permalink raw reply related
* [PATCH 4/8] clk: mmp2: add SP clock
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
The "security processor", sometimes referred to as "wireless trusted
module" or "generic encrypt unit" is a low-power core present on MMP2,
that has nothing to do with security, wireless, trust or encryption.
On an OLPC machine it runs CForth and serves as a keyboard controller:
http://dev.laptop.org/git/users/wmb/cforth/tree/src/app/arm-xo-1.75/ps2.fth
The register address was obtained from the OLPC kernel, since the
datasheet seems to be the Marvell's most important business secret.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
drivers/clk/mmp/clk-of-mmp2.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/clk/mmp/clk-of-mmp2.c b/drivers/clk/mmp/clk-of-mmp2.c
index 0fc75c395957..1da50f827a15 100644
--- a/drivers/clk/mmp/clk-of-mmp2.c
+++ b/drivers/clk/mmp/clk-of-mmp2.c
@@ -53,6 +53,7 @@
#define APMU_DISP1 0x110
#define APMU_CCIC0 0x50
#define APMU_CCIC1 0xf4
+#define APMU_SP 0x68
#define MPMU_UART_PLL 0x14
struct mmp2_clk_unit {
@@ -209,6 +210,8 @@ static struct mmp_clk_mix_config ccic1_mix_config = {
.reg_info = DEFINE_MIX_REG_INFO(4, 16, 2, 6, 32),
};
+static DEFINE_SPINLOCK(sp_lock);
+
static struct mmp_param_mux_clk apmu_mux_clks[] = {
{MMP2_CLK_DISP0_MUX, "disp0_mux", disp_parent_names, ARRAY_SIZE(disp_parent_names), CLK_SET_RATE_PARENT, APMU_DISP0, 6, 2, 0, &disp0_lock},
{MMP2_CLK_DISP1_MUX, "disp1_mux", disp_parent_names, ARRAY_SIZE(disp_parent_names), CLK_SET_RATE_PARENT, APMU_DISP1, 6, 2, 0, &disp1_lock},
@@ -239,6 +242,7 @@ static struct mmp_param_gate_clk apmu_gate_clks[] = {
{MMP2_CLK_CCIC1, "ccic1_clk", "ccic1_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x1b, 0x1b, 0x0, 0, &ccic1_lock},
{MMP2_CLK_CCIC1_PHY, "ccic1_phy_clk", "ccic1_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x24, 0x24, 0x0, 0, &ccic1_lock},
{MMP2_CLK_CCIC1_SPHY, "ccic1_sphy_clk", "ccic1_sphy_div", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x300, 0x300, 0x0, 0, &ccic1_lock},
+ {MMP2_CLK_SP, "sp_clk", NULL, CLK_SET_RATE_PARENT, APMU_SP, 0x1b, 0x1b, 0x0, 0, &sp_lock},
};
static void mmp2_axi_periph_clk_init(struct mmp2_clk_unit *pxa_unit)
--
2.17.1
^ permalink raw reply related
* [PATCH 3/8] dt-bindings: marvell,mmp2: Add clock id for the SP clock
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
This is the clock for the "security processor" core.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
include/dt-bindings/clock/marvell,mmp2.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/dt-bindings/clock/marvell,mmp2.h b/include/dt-bindings/clock/marvell,mmp2.h
index 228a5e234af0..7b24fc791146 100644
--- a/include/dt-bindings/clock/marvell,mmp2.h
+++ b/include/dt-bindings/clock/marvell,mmp2.h
@@ -71,6 +71,7 @@
#define MMP2_CLK_CCIC1_MIX 117
#define MMP2_CLK_CCIC1_PHY 118
#define MMP2_CLK_CCIC1_SPHY 119
+#define MMP2_CLK_SP 120
#define MMP2_NR_CLKS 200
#endif
--
2.17.1
^ permalink raw reply related
* [PATCH 2/8] dt-bindings: olpc,ap-sp: add GPIO lines
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
Add properties describing the GPIO lines used by the keyboard controller.
The olpc-apsp driver will do happily without them, but they are still part
of the hardware description. The driver could still reserve the lines,
so that nothing else touches them.
This makes the device node almost compatible with "ps2-gpio". I'm not
adding a compatible property, because ps2-gpio would use a different
interrupt, so that we'd need to name it. And I haven't actually tried it.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
Documentation/devicetree/bindings/serio/olpc,ap-sp.txt | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
index 36603419d6f8..2b1b1dbb54c1 100644
--- a/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
+++ b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
@@ -7,6 +7,10 @@ Required properties:
- clocks : phandle + clock-specifier for the clock that drives the WTM
- clock-names: should be "sp"
+Optional properties:
+- data-gpios : GPIO line used for PS/2 interface data
+- clk-gpios : GPIO line used for PS/2 interface clock
+
Example:
ap-sp@d4290000 {
compatible = "olpc,ap-sp";
@@ -14,4 +18,6 @@ Example:
interrupts = <40>;
clocks = <&soc_clocks MMP2_CLK_SP>;
clock-names = "sp";
+ data-gpios = <&gpio 72 GPIO_ACTIVE_HIGH>;
+ clk-gpios = <&gpio 71 GPIO_ACTIVE_HIGH>;
}
--
2.17.1
^ permalink raw reply related
* [PATCH 1/8] dt-bindings: olpc,ap-sp: add clock
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd, Lubomir Rintel
In-Reply-To: <20180910112654.42061-1-lkundrak@v3.sk>
The clock is necessary for the device operation, hence it's required.
Its name, "sp", stands for "Security Processor". It is one of several
names that are used for the processor that serves as the keyboard
controller on an OLPC and is consistent with the node name.
Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
Documentation/devicetree/bindings/serio/olpc,ap-sp.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
index 0e72183f52bc..36603419d6f8 100644
--- a/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
+++ b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
@@ -4,10 +4,14 @@ Required properties:
- compatible : "olpc,ap-sp"
- reg : base address and length of SoC's WTM registers
- interrupts : SP-AP interrupt
+- clocks : phandle + clock-specifier for the clock that drives the WTM
+- clock-names: should be "sp"
Example:
ap-sp@d4290000 {
compatible = "olpc,ap-sp";
reg = <0xd4290000 0x1000>;
interrupts = <40>;
+ clocks = <&soc_clocks MMP2_CLK_SP>;
+ clock-names = "sp";
}
--
2.17.1
^ permalink raw reply related
* [PATCH 0/8] OLPC 1.75 Keyboard/Touchpad fixes
From: Lubomir Rintel @ 2018-09-10 11:26 UTC (permalink / raw)
To: linux-kernel
Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
Dmitry Torokhov, Michael Turquette, Stephen Boyd
This makes keyboard/touchpad work on a DT MMP2 platform.
^ permalink raw reply
* Re: [PATCH] Input: wm97xx: only unregister wm97xx_driver if it has been registered
From: Robert Jarzmik @ 2018-09-09 19:08 UTC (permalink / raw)
To: Charles Keepax
Cc: Colin King, Charles Keepax, Mark Brown, Mauro Carvalho Chehab,
patches, linux-input, kernel-janitors, linux-kernel
In-Reply-To: <20180905091609.GC1653@imbe.wolfsonmicro.main>
Charles Keepax <ckeepax@opensource.cirrus.com> writes:
> On Tue, Sep 04, 2018 at 07:35:05PM +0100, Colin King wrote:
>> From: Colin Ian King <colin.king@canonical.com>
>>
>> In the case where IS_BUILTIN(CONFIG_AC97_BUS)) is not true, the
>> wm97xx_driver driver is being unregistered even it has not been
>> previously registered. Fix this by only unregistering it if
>> IS_BUILTIN(CONFIG_AC97_BUS)) is true. This fixes the warning
>> message:
>>
>> [ 834.111248] ------------[ cut here ]------------
>> [ 834.111248] Unexpected driver unregister!
>> [ 834.111319] WARNING: CPU: 2 PID: 11749 at drivers/base/driver.c:193 driver_unregister+0x3b/0x40
>> [ 834.111319] Modules linked in: wm97xx_ts(-) ....
>>
>> ..and a stack trace.
>>
>> To easily reproduce this, load and unload the module on a system where
>> the hardware is not supported.
>>
>> Fixes: ae9d1b5fbd7b ("Input: wm97xx: add new AC97 bus support")
>> Signed-off-by: Colin Ian King <colin.king@canonical.com>
>> ---
>
> Acked-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Acked-by: Robert Jarzmik <robert.jarzmik@free.fr>
Cheers.
--
Robert
^ permalink raw reply
* Re: [PATCH v2] HID: i2c-hid: Don't reset device upon system resume
From: Hans de Goede @ 2018-09-09 10:50 UTC (permalink / raw)
To: Kai-Heng Feng, jikos
Cc: benjamin.tissoires, linux-input, linux-kernel, Aaron Ma,
AceLan Kao
In-Reply-To: <20180906025518.30191-1-kai.heng.feng@canonical.com>
Hi,
On 06-09-18 04:55, Kai-Heng Feng wrote:
> Raydium touchscreen triggers interrupt storm after system-wide suspend:
> [ 179.085033] i2c_hid i2c-CUST0000:00: i2c_hid_get_input: incomplete
> report (58/65535)
>
> According to Raydium, Windows driver does not reset the device after
> system resume.
>
> The HID over I2C spec does specify a reset should be used at
> intialization, but it doesn't specify if reset is required for system
> suspend.
>
> Tested this patch on other i2c-hid touchpanels I have and those
> touchpanels do work after S3 without doing reset. If any regression
> happens to other touchpanel vendors, we can use quirk for Raydium
> devices.
>
> There's still one device uses I2C_HID_QUIRK_RESEND_REPORT_DESCR so keep
> it there.
I added that quirk and I still have the hardware for it. I've tested
things with the quirk remove and the touchscreen works after resume
now without the quirk.
I also have some other devices with a SIS i2c-hid touchscreen which
would no longer work after resume where the quirk did not help.
I had looking into those on my TODO but did not get around to
this yet. I've re-tested those with this patch and I'm happy to report
that this patch also fixes resume on the SIS touchscreens in the
following models:
Asus T100HA
Asus T200TA
Peaq C1010
Regards,
Hans
>
> Cc: Aaron Ma <aaron.ma@canonical.com>
> Cc: AceLan Kao <acelan.kao@canonical.com>
> Signed-off-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
> ---
> v2:
> Remove Raydium devices' ID and quirk.
> Rewording.
>
> drivers/hid/hid-ids.h | 4 ----
> drivers/hid/i2c-hid/i2c-hid.c | 13 +++++++------
> 2 files changed, 7 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 7da93d789080..e254ae802688 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -530,10 +530,6 @@
> #define I2C_VENDOR_ID_HANTICK 0x0911
> #define I2C_PRODUCT_ID_HANTICK_5288 0x5288
>
> -#define I2C_VENDOR_ID_RAYD 0x2386
> -#define I2C_PRODUCT_ID_RAYD_3118 0x3118
> -#define I2C_PRODUCT_ID_RAYD_4B33 0x4B33
> -
> #define USB_VENDOR_ID_HANWANG 0x0b57
> #define USB_DEVICE_ID_HANWANG_TABLET_FIRST 0x5000
> #define USB_DEVICE_ID_HANWANG_TABLET_LAST 0x8fff
> diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
> index 57126f6837bb..f3076659361a 100644
> --- a/drivers/hid/i2c-hid/i2c-hid.c
> +++ b/drivers/hid/i2c-hid/i2c-hid.c
> @@ -170,12 +170,8 @@ static const struct i2c_hid_quirks {
> I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
> { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288,
> I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
> - { I2C_VENDOR_ID_RAYD, I2C_PRODUCT_ID_RAYD_3118,
> - I2C_HID_QUIRK_RESEND_REPORT_DESCR },
> { USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS10FB_TOUCH,
> I2C_HID_QUIRK_RESEND_REPORT_DESCR },
> - { I2C_VENDOR_ID_RAYD, I2C_PRODUCT_ID_RAYD_4B33,
> - I2C_HID_QUIRK_RESEND_REPORT_DESCR },
> { 0, 0 }
> };
>
> @@ -1237,11 +1233,16 @@ static int i2c_hid_resume(struct device *dev)
> pm_runtime_enable(dev);
>
> enable_irq(client->irq);
> - ret = i2c_hid_hwreset(client);
> +
> + /* Instead of resetting device, simply powers the device on. This
> + * solves "incomplete reports" on Raydium devices 2386:3118 and
> + * 2386:4B33
> + */
> + ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
> if (ret)
> return ret;
>
> - /* RAYDIUM device (2386:3118) need to re-send report descr cmd
> + /* Some devices need to re-send report descr cmd
> * after resume, after this it will be back normal.
> * otherwise it issues too many incomplete reports.
> */
>
^ permalink raw reply
* Re: [PATCH V2 4/8] regulator: stpmic1: add stpmic1 regulator driver
From: Antonio Borneo @ 2018-09-08 9:37 UTC (permalink / raw)
To: p.paillet
Cc: Dmitry Torokhov, Rob Herring, Mark Rutland, lee.jones,
Liam Girdwood, Mark Brown, wim, linux, linux-input, devicetree,
linux-kernel, linux-watchdog, benjamin.gaignard, eballetbo
In-Reply-To: <1536325173-16617-5-git-send-email-p.paillet@st.com>
On Fri, Sep 7, 2018 at 3:15 PM Pascal PAILLET-LME <p.paillet@st.com> wrote:
>
> From: pascal paillet <p.paillet@st.com>
>
> The stpmic1 PMIC embeds several regulators and witches with
> different capabilities.
While fixing Kconfig, as Mark suggest, would you mind to fix also the
typo s/witches/switches/ in the commit message?
Antonio
^ permalink raw reply
* Re: [PATCH 1/2] input: atakbd.c - fix Atari keymap
From: Michael Schmitz @ 2018-09-08 8:06 UTC (permalink / raw)
To: Andreas Schwab, Finn Thain; +Cc: linux-input, debian-68k, linux-m68k
In-Reply-To: <871sa5k6gp.fsf@igel.home>
Thanks Andreas!
Cheers,
Michael
Am 07.09.2018 um 21:22 schrieb Andreas Schwab:
> Signed-off-by: Andreas Schwab <schwab@linux-m68k.org>
>
> Andreas.
>
^ permalink raw reply
* Re: [PATCH] Input: reserve 2 events code because of HID
From: Peter Hutterer @ 2018-09-08 1:44 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Dmitry Torokhov, Jiri Kosina, Harry Cutts, linux-input,
linux-kernel, Benjamin Tissoires
In-Reply-To: <20180907085115.30242-1-benjamin.tissoires@gmail.com>
On Fri, Sep 07, 2018 at 10:51:15AM +0200, Benjamin Tissoires wrote:
> From: Benjamin Tissoires <benjamin.tissoires@redhat.com>
>
> Prior to commit 190d7f02ce8e ("HID: input: do not increment usages when
> a duplicate is found") from the v4.18 kernel, HID used to shift the
> event codes if a duplicate usage was found. This ended up in a situation
> where a device would export a ton of ABS_MISC+n event codes, or a ton
> of REL_MISC+n event codes.
>
> This is now fixed, however userspace needs to detect those situation.
> Fortunately, ABS_MISC+1 was never assigned a code, and so libinput
> can detect fake multitouch devices from genuine ones by checking is
> ABS_MISC+1 is set.
sorry, this isn't quite correct. we use ABS_MT_SLOT - 1 (0x2e) for the
detection of fake MT devices, i.e.
if (ABS_MT_SLOT and not ABS_MT_SLOT-1) then multitouch
That gives you up to ABS_MISC + 6 for legitimate usage. this is handled by
libevdev, not libinput directly. libevdev adjusts the various bits on "is
this device an MT device" based on whether ABS_MT_SLOT-1 is set.
I can change this to also check for (ABS_MISC and not ABS_MISC+1) but that
obviously will depend on updated libraries then. Though I guess it won't
really be an issue until we fill up the other codes up to including 0x2e
with real values and expect userspace to handle those.
None of the bits I maintain have special code for REL_MISC+n so that bit
works fine, IMO.
One request though: instead of just having the value reserved, can we make
it REL_RESERVED and ABS_RESERVED please? Or ABS_CANARY :) Much easier than
hardcoding the numeric value.
Cheers,
Peter
> Now that we have REL_WHEEL_HI_RES, libinput won't be able to differentiate
> true high res mice from some other device in a pre-v4.18 kernel.
>
> Set in stone that the ABS_MISC+1 and REL_MISC+1 are reserved and should not
> be used so userspace can properly work around those old kernels.
>
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
>
> Hi,
>
> while reviewing my local tree, I realize that we might want to be able
> to differentiate older kernels from new ones that export REL_WHEEL_HI_RES.
>
> I know Dmitry was against adding several REL_MISC, so I hope just moving
> REL_WHEEL_HI_RES by one and reserving the faulty event codes would be good
> this time.
>
> This patch applies on top of the branch for-4.20/logitech-highres from
> Jiri's tree. It should go through Jiri's tree as well.
>
> Cheers,
> Benjamin
>
> include/uapi/linux/input-event-codes.h | 13 ++++++++++++-
> 1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
> index 29fb891ea337..30149939249a 100644
> --- a/include/uapi/linux/input-event-codes.h
> +++ b/include/uapi/linux/input-event-codes.h
> @@ -708,7 +708,12 @@
> #define REL_DIAL 0x07
> #define REL_WHEEL 0x08
> #define REL_MISC 0x09
> -#define REL_WHEEL_HI_RES 0x0a
> +/*
> + * 0x0a is reserved and should not be used.
> + * It was used by HID as REL_MISC+1 and usersapce needs to detect if
> + * the next REL_* event is correct or is just REL_MISC + n.
> + */
> +#define REL_WHEEL_HI_RES 0x0b
> #define REL_MAX 0x0f
> #define REL_CNT (REL_MAX+1)
>
> @@ -745,6 +750,12 @@
>
> #define ABS_MISC 0x28
>
> +/*
> + * 0x29 is reserved and should not be used.
> + * It was used by HID as ABS_MISC+1 and usersapce needs to detect if
> + * the next ABS_* event is correct or is just ABS_MISC + n.
> + */
> +
> #define ABS_MT_SLOT 0x2f /* MT slot being modified */
> #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
> #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
> --
> 2.14.3
>
^ permalink raw reply
* Re: [PATCH V2 6/8] input: stpmic1: add stpmic1 onkey driver
From: dmitry.torokhov @ 2018-09-07 17:38 UTC (permalink / raw)
To: Pascal PAILLET-LME
Cc: robh+dt@kernel.org, mark.rutland@arm.com, lee.jones@linaro.org,
lgirdwood@gmail.com, broonie@kernel.org, wim@linux-watchdog.org,
linux@roeck-us.net, linux-input@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-watchdog@vger.kernel.org, benjamin.gaignard@linaro.org,
eballetbo@gmail.com
In-Reply-To: <1536325173-16617-7-git-send-email-p.paillet@st.com>
Hi Pascal,
On Fri, Sep 07, 2018 at 12:59:45PM +0000, Pascal PAILLET-LME wrote:
> From: pascal paillet <p.paillet@st.com>
>
> The stpmic1 pmic is able to manage an onkey button. This driver exposes
> the stpmic1 onkey as an input device. It can also be configured to
> shut-down the power supplies on a long key-press with an adjustable
> duration.
>
> Signed-off-by: pascal paillet <p.paillet@st.com>
> ---
> changes in v2:
> * the hardware component has been renamed from stpmu1 to stpmic1 !
> * change headers
> * handle remarks from Dmitry
> * the irq is threaded because is is nested in a thread; I have added a comment.
> Dmitry, I'm sorry, but I did not catch your comment regarding usage of
> "generic device property API.". could you tell more ?
You basically do
s/of_property_/device_property_/
and that's it.
>
> drivers/input/misc/Kconfig | 11 ++
> drivers/input/misc/Makefile | 2 +
> drivers/input/misc/stpmic1_onkey.c | 257 +++++++++++++++++++++++++++++++++++++
> 3 files changed, 270 insertions(+)
> create mode 100644 drivers/input/misc/stpmic1_onkey.c
>
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index c25606e..cc82dad 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -841,4 +841,15 @@ config INPUT_RAVE_SP_PWRBUTTON
> To compile this driver as a module, choose M here: the
> module will be called rave-sp-pwrbutton.
>
> +config INPUT_STPMIC1_ONKEY
> + tristate "STPMIC1 PMIC Onkey support"
> + depends on MFD_STPMIC1
> + help
> + Say Y to enable support of onkey embedded into STPMIC1 PMIC. onkey
> + can be used to wakeup from low power modes and force a shut-down on
> + long press.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called stpmic1_onkey.
> +
> endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 72cde28..f0e11b0 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -70,6 +70,7 @@ obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
> obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
> obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
> obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
> +obj-$(CONFIG_INPUT_STPMIC1_ONKEY) += stpmic1_onkey.o
> obj-$(CONFIG_INPUT_TPS65218_PWRBUTTON) += tps65218-pwrbutton.o
> obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
> obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
> @@ -80,3 +81,4 @@ obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
> obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
> obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
> +
> diff --git a/drivers/input/misc/stpmic1_onkey.c b/drivers/input/misc/stpmic1_onkey.c
> new file mode 100644
> index 0000000..170d879
> --- /dev/null
> +++ b/drivers/input/misc/stpmic1_onkey.c
> @@ -0,0 +1,257 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (C) STMicroelectronics 2018
> +// Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
> +
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/stpmic1.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +/**
> + * struct stpmic1_onkey - OnKey data
> + * @pmic: pointer to STPMIC1 PMIC device
> + * @input_dev: pointer to input device
> + * @irq_falling: irq that we are hooked on to
> + * @irq_rising: irq that we are hooked on to
> + */
> +struct stpmic1_onkey {
> + struct stpmic1_dev *pmic;
> + struct input_dev *input_dev;
> + int irq_falling;
> + int irq_rising;
> +};
> +
> +/**
> + * struct pmic_onkey_config - configuration of pmic PONKEYn
> + * @turnoff_enabled: value to enable turnoff condition
> + * @cc_flag_clear: value to clear CC flag in case of PowerOff
> + * trigger by longkey press
> + * @onkey_pullup_val: value of PONKEY PullUp (active or inactive)
> + * @long_press_time_val: value for long press h/w shutdown event
> + */
> +struct pmic_onkey_config {
> + bool turnoff_enabled;
> + bool cc_flag_clear;
> + u8 onkey_pullup_val;
> + u8 long_press_time_val;
> +};
> +
> +static irqreturn_t onkey_falling_irq(int irq, void *ponkey)
> +{
> + struct stpmic1_onkey *onkey = ponkey;
> + struct input_dev *input_dev = onkey->input_dev;
> +
> + input_report_key(input_dev, KEY_POWER, 1);
> + pm_wakeup_event(input_dev->dev.parent, 0);
> + input_sync(input_dev);
> +
> + dev_dbg(&input_dev->dev, "Pwr Onkey Falling Interrupt received\n");
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t onkey_rising_irq(int irq, void *ponkey)
> +{
> + struct stpmic1_onkey *onkey = ponkey;
> + struct input_dev *input_dev = onkey->input_dev;
> +
> + input_report_key(input_dev, KEY_POWER, 0);
> + pm_wakeup_event(input_dev->dev.parent, 0);
> + input_sync(input_dev);
> +
> + dev_dbg(&input_dev->dev, "Pwr Onkey Rising Interrupt received\n");
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int stpmic1_onkey_dt_params(struct platform_device *pdev,
> + struct stpmic1_onkey *onkey,
> + struct pmic_onkey_config *config)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np;
> + u32 val;
> +
> + np = dev->of_node;
> +
> + onkey->irq_falling = platform_get_irq_byname(pdev, "onkey-falling");
> + if (onkey->irq_falling < 0) {
> + dev_err(dev, "failed: request IRQ onkey-falling %d\n",
> + onkey->irq_falling);
> + return onkey->irq_falling;
> + }
> +
> + onkey->irq_rising = platform_get_irq_byname(pdev, "onkey-rising");
> + if (onkey->irq_rising < 0) {
> + dev_err(dev, "failed: request IRQ onkey-rising %d\n",
> + onkey->irq_rising);
> + return onkey->irq_rising;
> + }
> +
> + if (!of_property_read_u32(np, "st,onkey-long-press-seconds", &val)) {
> + if (val >= 1 && val <= 16) {
> + config->long_press_time_val = (16 - val);
> + } else {
> + dev_warn(dev,
> + "Invalid range of long key pressed timer %d (<16)\n",
> + val);
> + }
> + }
> + if (of_get_property(np, "st,onkey-pwroff-enabled", NULL))
> + config->turnoff_enabled = true;
> +
> + if (of_get_property(np, "st,onkey-clear-cc-flag", NULL))
> + config->cc_flag_clear = true;
> +
> + if (of_get_property(np, "st,onkey-pu-inactive", NULL))
> + config->onkey_pullup_val = PONKEY_PU_ACTIVE;
> +
> + dev_dbg(dev, "onkey-switch-off duration=%d seconds\n",
> + config->long_press_time_val);
> +
> + return 0;
> +}
> +
> +static int stpmic1_onkey_probe(struct platform_device *pdev)
> +{
> + struct stpmic1_dev *pmic = dev_get_drvdata(pdev->dev.parent);
> + struct device *dev = &pdev->dev;
> + struct input_dev *input_dev;
> + struct stpmic1_onkey *onkey;
> + struct pmic_onkey_config config;
> + unsigned int val = 0;
> + int error;
> +
> + onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL);
> + if (!onkey)
> + return -ENOMEM;
> +
> + memset(&config, 0, sizeof(struct pmic_onkey_config));
> + error = stpmic1_onkey_dt_params(pdev, onkey, &config);
> + if (error)
> + return error;
> +
> + input_dev = devm_input_allocate_device(dev);
> + if (!input_dev) {
> + dev_err(dev, "Can't allocate Pwr Onkey Input Device\n");
> + return -ENOMEM;
> + }
> +
> + input_dev->name = "pmic_onkey";
> + input_dev->phys = "pmic_onkey/input0";
> +
> + input_set_capability(input_dev, EV_KEY, KEY_POWER);
> +
> + /* Setup Power Onkey Hardware parameters (long key press)*/
> + val = (config.long_press_time_val & PONKEY_TURNOFF_TIMER_MASK);
> + if (config.turnoff_enabled)
> + val |= PONKEY_PWR_OFF;
> + if (config.cc_flag_clear)
> + val |= PONKEY_CC_FLAG_CLEAR;
> + error = regmap_update_bits(pmic->regmap, PKEY_TURNOFF_CR,
> + PONKEY_TURNOFF_MASK, val);
> + if (error) {
> + dev_err(dev, "LONG_PRESS_KEY_UPDATE failed: %d\n", error);
> + return error;
> + }
> +
> + error = regmap_update_bits(pmic->regmap, PADS_PULL_CR,
> + PONKEY_PU_ACTIVE,
> + config.onkey_pullup_val);
> + if (error) {
> + dev_err(dev, "ONKEY Pads configuration failed: %d\n", error);
> + return error;
> + }
> +
> + onkey->pmic = pmic;
> + onkey->input_dev = input_dev;
> +
> + /* interrupt is nested in a thread */
> + error = devm_request_threaded_irq(dev, onkey->irq_falling, NULL,
> + onkey_falling_irq, IRQF_ONESHOT,
> + dev_name(dev), onkey);
> + if (error) {
> + dev_err(dev, "Can't get IRQ Onkey Falling: %d\n", error);
> + return error;
> + }
> +
> + error = devm_request_threaded_irq(dev, onkey->irq_rising, NULL,
> + onkey_rising_irq, IRQF_ONESHOT,
> + dev_name(dev), onkey);
> + if (error) {
> + dev_err(dev, "Can't get IRQ Onkey Rising: %d\n", error);
> + return error;
> + }
> +
> + error = input_register_device(input_dev);
> + if (error) {
> + dev_err(dev, "Can't register power button: %d\n", error);
> + return error;
> + }
> +
> + platform_set_drvdata(pdev, onkey);
> + device_init_wakeup(dev, true);
> +
> + return 0;
> +}
> +
> +static int stpmic1_onkey_remove(struct platform_device *pdev)
> +{
> + struct stpmic1_onkey *onkey = platform_get_drvdata(pdev);
> +
> + input_unregister_device(onkey->input_dev);
> + return 0;
> +}
> +
> +static int __maybe_unused stpmic1_onkey_suspend(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct stpmic1_onkey *onkey = platform_get_drvdata(pdev);
> +
> + if (device_may_wakeup(dev)) {
> + enable_irq_wake(onkey->irq_falling);
> + enable_irq_wake(onkey->irq_rising);
> + }
> + return 0;
> +}
> +
> +static int __maybe_unused stpmic1_onkey_resume(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct stpmic1_onkey *onkey = platform_get_drvdata(pdev);
> +
> + if (device_may_wakeup(dev)) {
> + disable_irq_wake(onkey->irq_falling);
> + disable_irq_wake(onkey->irq_rising);
> + }
> + return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(stpmic1_onkey_pm,
> + stpmic1_onkey_suspend,
> + stpmic1_onkey_resume);
> +
> +static const struct of_device_id of_stpmic1_onkey_match[] = {
> + { .compatible = "st,stpmic1-onkey" },
> + { },
> +};
> +
> +MODULE_DEVICE_TABLE(of, of_stpmic1_onkey_match);
> +
> +static struct platform_driver stpmic1_onkey_driver = {
> + .probe = stpmic1_onkey_probe,
> + .remove = stpmic1_onkey_remove,
> + .driver = {
> + .name = "stpmic1_onkey",
> + .of_match_table = of_match_ptr(of_stpmic1_onkey_match),
> + .pm = &stpmic1_onkey_pm,
> + },
> +};
> +module_platform_driver(stpmic1_onkey_driver);
> +
> +MODULE_DESCRIPTION("Onkey driver for STPMIC1");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Pascal Paillet");
> --
> 1.9.1
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: reserve 2 events code because of HID
From: Dmitry Torokhov @ 2018-09-07 17:35 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Jiri Kosina, Harry Cutts, Peter Hutterer, linux-input,
linux-kernel, Benjamin Tissoires
In-Reply-To: <20180907085115.30242-1-benjamin.tissoires@gmail.com>
On Fri, Sep 07, 2018 at 10:51:15AM +0200, Benjamin Tissoires wrote:
> From: Benjamin Tissoires <benjamin.tissoires@redhat.com>
>
> Prior to commit 190d7f02ce8e ("HID: input: do not increment usages when
> a duplicate is found") from the v4.18 kernel, HID used to shift the
> event codes if a duplicate usage was found. This ended up in a situation
> where a device would export a ton of ABS_MISC+n event codes, or a ton
> of REL_MISC+n event codes.
>
> This is now fixed, however userspace needs to detect those situation.
> Fortunately, ABS_MISC+1 was never assigned a code, and so libinput
> can detect fake multitouch devices from genuine ones by checking is
> ABS_MISC+1 is set.
>
> Now that we have REL_WHEEL_HI_RES, libinput won't be able to differentiate
> true high res mice from some other device in a pre-v4.18 kernel.
>
> Set in stone that the ABS_MISC+1 and REL_MISC+1 are reserved and should not
> be used so userspace can properly work around those old kernels.
>
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
>
> Hi,
>
> while reviewing my local tree, I realize that we might want to be able
> to differentiate older kernels from new ones that export REL_WHEEL_HI_RES.
>
> I know Dmitry was against adding several REL_MISC, so I hope just moving
> REL_WHEEL_HI_RES by one and reserving the faulty event codes would be good
> this time.
>
> This patch applies on top of the branch for-4.20/logitech-highres from
> Jiri's tree. It should go through Jiri's tree as well.
Hm, this is a bit ugly, bit I guess we could spare an event code.
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
>
> Cheers,
> Benjamin
>
> include/uapi/linux/input-event-codes.h | 13 ++++++++++++-
> 1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
> index 29fb891ea337..30149939249a 100644
> --- a/include/uapi/linux/input-event-codes.h
> +++ b/include/uapi/linux/input-event-codes.h
> @@ -708,7 +708,12 @@
> #define REL_DIAL 0x07
> #define REL_WHEEL 0x08
> #define REL_MISC 0x09
> -#define REL_WHEEL_HI_RES 0x0a
> +/*
> + * 0x0a is reserved and should not be used.
> + * It was used by HID as REL_MISC+1 and usersapce needs to detect if
> + * the next REL_* event is correct or is just REL_MISC + n.
> + */
> +#define REL_WHEEL_HI_RES 0x0b
> #define REL_MAX 0x0f
> #define REL_CNT (REL_MAX+1)
>
> @@ -745,6 +750,12 @@
>
> #define ABS_MISC 0x28
>
> +/*
> + * 0x29 is reserved and should not be used.
> + * It was used by HID as ABS_MISC+1 and usersapce needs to detect if
> + * the next ABS_* event is correct or is just ABS_MISC + n.
> + */
> +
> #define ABS_MT_SLOT 0x2f /* MT slot being modified */
> #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
> #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
> --
> 2.14.3
>
--
Dmitry
^ permalink raw reply
* Re: [PATCH 6/7] HID: logitech-hidpp: support the G700 over wireless
From: Harry Cutts @ 2018-09-07 17:33 UTC (permalink / raw)
To: benjamin.tissoires
Cc: jikos, Nestor Lopez Casado, simon, Olivier Gay, linux-input,
linux-kernel
In-Reply-To: <20180907103450.13890-7-benjamin.tissoires@redhat.com>
Hi Benjamin,
On Fri, 7 Sep 2018 at 03:35, Benjamin Tissoires
<benjamin.tissoires@redhat.com> wrote:
>
> The G700 is using a non unifying receiver, so it's easy to add its support
> in hid-logitech-hidpp now.
> [snip]
> @@ -3671,6 +3671,9 @@ static const struct hid_device_id hidpp_devices[] = {
> { /* Solar Keyboard Logitech K750 */
> LDJ_DEVICE(0x4002),
> .driver_data = HIDPP_QUIRK_CLASS_K750 },
> + { /* G700 over Wireless */
> + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G700_RECEIVER),
> + .driver_data = HIDPP_QUIRK_RECEIVER | HIDPP_QUIRK_UNIFYING },
As someone who's new to the codebase, it seems rather confusing to me
that HIDPP_QUIRK_UNIFYING would be present here for a device that
doesn't use a Unifying receiver. Am I misunderstanding, or should we
consider renaming the quirk or adding some clarifying comment?
(Similarly for the G900 in the next patch.)
>
> { LDJ_DEVICE(HID_ANY_ID) },
>
> --
> 2.14.3
>
Thanks,
Harry Cutts
Chrome OS Touch/Input team
^ permalink raw reply
* Re: [PATCH V2 4/8] regulator: stpmic1: add stpmic1 regulator driver
From: Mark Brown @ 2018-09-07 15:56 UTC (permalink / raw)
To: Pascal PAILLET-LME
Cc: dmitry.torokhov@gmail.com, robh+dt@kernel.org,
mark.rutland@arm.com, lee.jones@linaro.org, lgirdwood@gmail.com,
wim@linux-watchdog.org, linux@roeck-us.net,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-watchdog@vger.kernel.org,
benjamin.gaignard@linaro.org, eballetbo@gmail.com
In-Reply-To: <1536325173-16617-5-git-send-email-p.paillet@st.com>
[-- Attachment #1: Type: text/plain, Size: 482 bytes --]
On Fri, Sep 07, 2018 at 12:59:44PM +0000, Pascal PAILLET-LME wrote:
> @@ -804,6 +804,18 @@ config REGULATOR_TI_ABB
> on TI SoCs may be unstable without enabling this as it provides
> device specific optimized bias to allow/optimize functionality.
>
> +config REGULATOR_STPMIC1
This looks good to me except the Kconfig isn't alphabetically sorted and
has a conflict. If the series doesn't otherwise need a resend I should
be able to fix by hand, let me know.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH V2 8/8] watchdog: stpmic1: add stpmic1 watchdog driver
From: Guenter Roeck @ 2018-09-07 13:48 UTC (permalink / raw)
To: Pascal PAILLET-LME, dmitry.torokhov@gmail.com, robh+dt@kernel.org,
mark.rutland@arm.com, lee.jones@linaro.org, lgirdwood@gmail.com,
broonie@kernel.org, wim@linux-watchdog.org,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-watchdog@vger.kernel.org,
benjamin.gaignard@linaro.org, eballetbo@gmail.com
In-Reply-To: <1536325173-16617-9-git-send-email-p.paillet@st.com>
On 09/07/2018 05:59 AM, Pascal PAILLET-LME wrote:
> From: pascal paillet <p.paillet@st.com>
>
> The stpmic1 PMIC embeds a watchdog which is disabled by default. As soon
> as the watchdog is started, it must be refreshed periodically otherwise
> the PMIC goes off.
>
> Signed-off-by: pascal paillet <p.paillet@st.com>
> ---
> changes in v2:
> * the hardware component has been renamed from stpmu1 to stpmic1 !
> * change headers
> * handle remarks from Guenter
Hmm ... as if I would remember what those were.
> * fix set timeout
>
> drivers/watchdog/Kconfig | 12 ++++
> drivers/watchdog/Makefile | 1 +
> drivers/watchdog/stpmic1_wdt.c | 130 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 143 insertions(+)
> create mode 100644 drivers/watchdog/stpmic1_wdt.c
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 9af07fd..11c5937 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -796,6 +796,18 @@ config STM32_WATCHDOG
> To compile this driver as a module, choose M here: the
> module will be called stm32_iwdg.
>
> +config STPMIC1_WATCHDOG
> + tristate "STPMIC1 PMIC watchdog support"
> + depends on MFD_STPMIC1
> + select WATCHDOG_CORE
> + help
> + Say Y here to include watchdog support embedded into STPMIC1 PMIC.
> + If the watchdog timer expires, stpmic1 shut-down all its power
> + supplies.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called spmic1_wdt.
> +
> config UNIPHIER_WATCHDOG
> tristate "UniPhier watchdog support"
> depends on ARCH_UNIPHIER || COMPILE_TEST
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 1d3c6b0..a898d8f 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -216,3 +216,4 @@ obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
> obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
> obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
> obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
> +obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
> diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c
> new file mode 100644
> index 0000000..391e544
> --- /dev/null
> +++ b/drivers/watchdog/stpmic1_wdt.c
> @@ -0,0 +1,130 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (C) STMicroelectronics 2018
> +// Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
> + > +#include <linux/kernel.h>
> +#include <linux/mfd/stpmic1.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +#include <linux/reboot.h>
> +#include <linux/slab.h>
> +#include <linux/watchdog.h>
> +
> +/* WATCHDOG CONTROL REGISTER bit */
> +#define WDT_START BIT(0)
> +#define WDT_PING BIT(1)
> +#define WDT_START_MASK BIT(0)
> +#define WDT_PING_MASK BIT(1)
> +
> +#define PMIC_WDT_MIN_TIMEOUT 1
> +#define PMIC_WDT_MAX_TIMEOUT 256
> +
> +struct stpmic1_wdt {
> + struct stpmic1_dev *pmic;
> + struct watchdog_device wdtdev;
> +};
> +
> +static int pmic_wdt_start(struct watchdog_device *wdd)
> +{
> + struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + return regmap_update_bits(wdt->pmic->regmap,
> + WCHDG_CR, WDT_START_MASK, WDT_START);
> +}
> +
> +static int pmic_wdt_stop(struct watchdog_device *wdd)
> +{
> + struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + return regmap_update_bits(wdt->pmic->regmap,
> + WCHDG_CR, WDT_START_MASK, ~WDT_START);
> +}
> +
> +static int pmic_wdt_ping(struct watchdog_device *wdd)
> +{
> + struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + return regmap_update_bits(wdt->pmic->regmap,
> + WCHDG_CR, WDT_PING_MASK, WDT_PING);
> +}
> +
> +static int pmic_wdt_set_timeout(struct watchdog_device *wdd,
> + unsigned int timeout)
> +{
> + struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + return regmap_write(wdt->pmic->regmap, WCHDG_TIMER_CR, timeout - 1);
What happens if the configured timeout is 1 ? Does that translate to a 1-second
timeout ?
This function also has to set wdd->timeout.
> +}
> +
> +static const struct watchdog_info pmic_watchdog_info = {
> + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
No WDIOF_MAGICCLOSE ?
> + .identity = "STPMIC1 PMIC Watchdog",
> +};
> +
> +static const struct watchdog_ops pmic_watchdog_ops = {
> + .owner = THIS_MODULE,
> + .start = pmic_wdt_start,
> + .stop = pmic_wdt_stop,
> + .ping = pmic_wdt_ping,
> + .set_timeout = pmic_wdt_set_timeout,
> +};
> +
> +static int pmic_wdt_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct stpmic1_dev *pmic;
> + struct stpmic1_wdt *wdt;
> +
> + if (!pdev->dev.parent)
> + return -EINVAL;
> +
> + pmic = dev_get_drvdata(pdev->dev.parent);
> + if (!pmic)
> + return -EINVAL;
> +
> + wdt = devm_kzalloc(&pdev->dev, sizeof(struct stpmic1_wdt), GFP_KERNEL);
> + if (!wdt)
> + return -ENOMEM;
> +
> + wdt->pmic = pmic;
> +
> + wdt->wdtdev.info = &pmic_watchdog_info;
> + wdt->wdtdev.ops = &pmic_watchdog_ops;
> + wdt->wdtdev.min_timeout = PMIC_WDT_MIN_TIMEOUT;
> + wdt->wdtdev.max_timeout = PMIC_WDT_MAX_TIMEOUT;
> + wdt->wdtdev.timeout = PMIC_WDT_MIN_TIMEOUT;
Are you sure about that ? A timeout of 1 second may be a cause for unexpected
reboots if user space is busy. Personally I find it better to set a larger
default and let userspace configure a tighter value if so desired.
Also,
watchdog_init_timeout(&wdt->wdtdev, 0, &pdev->dev);
might be desirable if you want to be able to specify the timeout in the
devicetree file.
> +
> + wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
> +
> + watchdog_set_drvdata(&wdt->wdtdev, wdt);
> + dev_set_drvdata(&pdev->dev, wdt);
Unless I am missing something this should not be needed.
> +
> + ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdtdev);
> + if (ret)
> + return ret;
> +
> + dev_dbg(wdt->pmic->dev, "PMIC Watchdog driver probed\n");
> + return 0;
> +}
> +
> +static const struct of_device_id of_pmic_wdt_match[] = {
> + { .compatible = "st,stpmic1-wdt" },
> + { },
> +};
> +
> +MODULE_DEVICE_TABLE(of, of_pmic_wdt_match);
> +
> +static struct platform_driver stpmic1_wdt_driver = {
> + .probe = pmic_wdt_probe,
> + .driver = {
> + .name = "stpmic1-wdt",
> + .of_match_table = of_pmic_wdt_match,
> + },
> +};
> +module_platform_driver(stpmic1_wdt_driver);
> +
> +MODULE_DESCRIPTION("Watchdog driver for STPMIC1 device");
> +MODULE_AUTHOR("Pascal Paillet");
> +MODULE_LICENSE("GPL v2");
>
^ permalink raw reply
* [PATCH V2 8/8] watchdog: stpmic1: add stpmic1 watchdog driver
From: Pascal PAILLET-LME @ 2018-09-07 12:59 UTC (permalink / raw)
To: dmitry.torokhov@gmail.com, robh+dt@kernel.org,
mark.rutland@arm.com, lee.jones@linaro.org, lgirdwood@gmail.com,
broonie@kernel.org, wim@linux-watchdog.org, linux@roeck-us.net,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-watchdog@vger.kernel.org,
benjamin.gaignard@linaro.org, eballetbo@gmail.com
Cc: Pascal PAILLET-LME
In-Reply-To: <1536325173-16617-1-git-send-email-p.paillet@st.com>
From: pascal paillet <p.paillet@st.com>
The stpmic1 PMIC embeds a watchdog which is disabled by default. As soon
as the watchdog is started, it must be refreshed periodically otherwise
the PMIC goes off.
Signed-off-by: pascal paillet <p.paillet@st.com>
---
changes in v2:
* the hardware component has been renamed from stpmu1 to stpmic1 !
* change headers
* handle remarks from Guenter
* fix set timeout
drivers/watchdog/Kconfig | 12 ++++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/stpmic1_wdt.c | 130 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 143 insertions(+)
create mode 100644 drivers/watchdog/stpmic1_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 9af07fd..11c5937 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -796,6 +796,18 @@ config STM32_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called stm32_iwdg.
+config STPMIC1_WATCHDOG
+ tristate "STPMIC1 PMIC watchdog support"
+ depends on MFD_STPMIC1
+ select WATCHDOG_CORE
+ help
+ Say Y here to include watchdog support embedded into STPMIC1 PMIC.
+ If the watchdog timer expires, stpmic1 shut-down all its power
+ supplies.
+
+ To compile this driver as a module, choose M here: the
+ module will be called spmic1_wdt.
+
config UNIPHIER_WATCHDOG
tristate "UniPhier watchdog support"
depends on ARCH_UNIPHIER || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 1d3c6b0..a898d8f 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -216,3 +216,4 @@ obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
+obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c
new file mode 100644
index 0000000..391e544
--- /dev/null
+++ b/drivers/watchdog/stpmic1_wdt.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) STMicroelectronics 2018
+// Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
+
+#include <linux/kernel.h>
+#include <linux/mfd/stpmic1.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+/* WATCHDOG CONTROL REGISTER bit */
+#define WDT_START BIT(0)
+#define WDT_PING BIT(1)
+#define WDT_START_MASK BIT(0)
+#define WDT_PING_MASK BIT(1)
+
+#define PMIC_WDT_MIN_TIMEOUT 1
+#define PMIC_WDT_MAX_TIMEOUT 256
+
+struct stpmic1_wdt {
+ struct stpmic1_dev *pmic;
+ struct watchdog_device wdtdev;
+};
+
+static int pmic_wdt_start(struct watchdog_device *wdd)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_START_MASK, WDT_START);
+}
+
+static int pmic_wdt_stop(struct watchdog_device *wdd)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_START_MASK, ~WDT_START);
+}
+
+static int pmic_wdt_ping(struct watchdog_device *wdd)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_PING_MASK, WDT_PING);
+}
+
+static int pmic_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_write(wdt->pmic->regmap, WCHDG_TIMER_CR, timeout - 1);
+}
+
+static const struct watchdog_info pmic_watchdog_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "STPMIC1 PMIC Watchdog",
+};
+
+static const struct watchdog_ops pmic_watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = pmic_wdt_start,
+ .stop = pmic_wdt_stop,
+ .ping = pmic_wdt_ping,
+ .set_timeout = pmic_wdt_set_timeout,
+};
+
+static int pmic_wdt_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct stpmic1_dev *pmic;
+ struct stpmic1_wdt *wdt;
+
+ if (!pdev->dev.parent)
+ return -EINVAL;
+
+ pmic = dev_get_drvdata(pdev->dev.parent);
+ if (!pmic)
+ return -EINVAL;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(struct stpmic1_wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->pmic = pmic;
+
+ wdt->wdtdev.info = &pmic_watchdog_info;
+ wdt->wdtdev.ops = &pmic_watchdog_ops;
+ wdt->wdtdev.min_timeout = PMIC_WDT_MIN_TIMEOUT;
+ wdt->wdtdev.max_timeout = PMIC_WDT_MAX_TIMEOUT;
+ wdt->wdtdev.timeout = PMIC_WDT_MIN_TIMEOUT;
+
+ wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+ watchdog_set_drvdata(&wdt->wdtdev, wdt);
+ dev_set_drvdata(&pdev->dev, wdt);
+
+ ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdtdev);
+ if (ret)
+ return ret;
+
+ dev_dbg(wdt->pmic->dev, "PMIC Watchdog driver probed\n");
+ return 0;
+}
+
+static const struct of_device_id of_pmic_wdt_match[] = {
+ { .compatible = "st,stpmic1-wdt" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, of_pmic_wdt_match);
+
+static struct platform_driver stpmic1_wdt_driver = {
+ .probe = pmic_wdt_probe,
+ .driver = {
+ .name = "stpmic1-wdt",
+ .of_match_table = of_pmic_wdt_match,
+ },
+};
+module_platform_driver(stpmic1_wdt_driver);
+
+MODULE_DESCRIPTION("Watchdog driver for STPMIC1 device");
+MODULE_AUTHOR("Pascal Paillet");
+MODULE_LICENSE("GPL v2");
--
1.9.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox