Message ID | 20250521-dev-adp5589-fw-v4-12-f2c988d7a7a0@analog.com |
---|---|
State | Superseded |
Headers | show |
Series | mfd: adp5585: support keymap events and drop legacy Input driver | expand |
On Wed, 21 May 2025, Nuno Sá via B4 Relay wrote: > From: Nuno Sá <nuno.sa@analog.com> > > These devices are capable of generate FIFO based events based on KEY or > GPI presses. Add support for handling these events. This is in > preparation of adding full support for keymap and gpis based events. > > Signed-off-by: Nuno Sá <nuno.sa@analog.com> > --- > drivers/mfd/adp5585.c | 184 +++++++++++++++++++++++++++++++++++++++++--- > include/linux/mfd/adp5585.h | 18 +++++ > 2 files changed, 190 insertions(+), 12 deletions(-) Reviewed-by: Lee Jones <lee@kernel.org> > diff --git a/drivers/mfd/adp5585.c b/drivers/mfd/adp5585.c > index e90d16389732f3d8790eb910acd82be2033eaa7e..dcc09c898dd7990b39e21cb2324fa66ae171a802 100644 > --- a/drivers/mfd/adp5585.c > +++ b/drivers/mfd/adp5585.c > @@ -8,6 +8,7 @@ > */ > > #include <linux/array_size.h> > +#include <linux/bitfield.h> > #include <linux/device.h> > #include <linux/err.h> > #include <linux/i2c.h> > @@ -166,10 +167,16 @@ static const struct regmap_config adp5589_regmap_config_template = { > > static const struct adp5585_regs adp5585_regs = { > .ext_cfg = ADP5585_PIN_CONFIG_C, > + .int_en = ADP5585_INT_EN, > + .gen_cfg = ADP5585_GENERAL_CFG, > + .poll_ptime_cfg = ADP5585_POLL_PTIME_CFG, > }; > > static const struct adp5585_regs adp5589_regs = { > .ext_cfg = ADP5589_PIN_CONFIG_D, > + .int_en = ADP5589_INT_EN, > + .gen_cfg = ADP5589_GENERAL_CFG, > + .poll_ptime_cfg = ADP5589_POLL_PTIME_CFG, > }; > > static int adp5585_fill_variant_config(struct adp5585_dev *adp5585, > @@ -244,6 +251,150 @@ static void adp5585_osc_disable(void *data) > regmap_write(adp5585->regmap, ADP5585_GENERAL_CFG, 0); > } > > +static void adp5585_report_events(struct adp5585_dev *adp5585, int ev_cnt) > +{ > + unsigned int i; > + > + for (i = 0; i < ev_cnt; i++) { > + unsigned long key_val, key_press; > + unsigned int key; > + int ret; > + > + ret = regmap_read(adp5585->regmap, ADP5585_FIFO_1 + i, &key); > + if (ret) > + return; > + > + key_val = FIELD_GET(ADP5585_KEY_EVENT_MASK, key); > + key_press = FIELD_GET(ADP5585_KEV_EV_PRESS_MASK, key); > + > + blocking_notifier_call_chain(&adp5585->event_notifier, key_val, (void *)key_press); > + } > +} > + > +static irqreturn_t adp5585_irq(int irq, void *data) > +{ > + struct adp5585_dev *adp5585 = data; > + unsigned int status, ev_cnt; > + int ret; > + > + ret = regmap_read(adp5585->regmap, ADP5585_INT_STATUS, &status); > + if (ret) > + return IRQ_HANDLED; > + > + if (status & ADP5585_OVRFLOW_INT) > + dev_err_ratelimited(adp5585->dev, "Event overflow error\n"); > + > + if (!(status & ADP5585_EVENT_INT)) > + goto out_irq; > + > + ret = regmap_read(adp5585->regmap, ADP5585_STATUS, &ev_cnt); > + if (ret) > + goto out_irq; > + > + ev_cnt = FIELD_GET(ADP5585_EC_MASK, ev_cnt); > + if (!ev_cnt) > + goto out_irq; > + > + adp5585_report_events(adp5585, ev_cnt); > +out_irq: > + regmap_write(adp5585->regmap, ADP5585_INT_STATUS, status); > + return IRQ_HANDLED; > +} > + > +static int adp5585_setup(struct adp5585_dev *adp5585) > +{ > + const struct adp5585_regs *regs = adp5585->regs; > + unsigned int reg_val, i; > + int ret; > + > + /* Clear any possible event by reading all the FIFO entries */ > + for (i = 0; i < ADP5585_EV_MAX; i++) { > + ret = regmap_read(adp5585->regmap, ADP5585_FIFO_1 + i, ®_val); > + if (ret) > + return ret; > + } > + > + ret = regmap_write(adp5585->regmap, regs->poll_ptime_cfg, adp5585->ev_poll_time); > + if (ret) > + return ret; > + > + /* > + * Enable the internal oscillator, as it's shared between multiple > + * functions. > + * > + * As a future improvement, power consumption could possibly be > + * decreased in some use cases by enabling and disabling the oscillator > + * dynamically based on the needs of the child drivers. > + */ > + ret = regmap_write(adp5585->regmap, regs->gen_cfg, > + ADP5585_OSC_FREQ_500KHZ | ADP5585_INT_CFG | ADP5585_OSC_EN); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(adp5585->dev, adp5585_osc_disable, adp5585); > +} > + > +static int adp5585_parse_fw(struct adp5585_dev *adp5585) > +{ > + unsigned int prop_val; > + int ret; > + > + ret = device_property_read_u32(adp5585->dev, "poll-interval", &prop_val); > + if (!ret) { > + adp5585->ev_poll_time = prop_val / 10 - 1; > + /* > + * ev_poll_time is the raw value to be written on the register and 0 to 3 are the > + * valid values. > + */ > + if (adp5585->ev_poll_time > 3) > + return dev_err_probe(adp5585->dev, -EINVAL, > + "Invalid value(%u) for poll-interval\n", prop_val); > + } > + > + return 0; > +} > + > +static void adp5585_irq_disable(void *data) > +{ > + struct adp5585_dev *adp5585 = data; > + > + regmap_write(adp5585->regmap, adp5585->regs->int_en, 0); > +} > + > +static int adp5585_irq_enable(struct i2c_client *i2c, > + struct adp5585_dev *adp5585) > +{ > + const struct adp5585_regs *regs = adp5585->regs; > + unsigned int stat; > + int ret; > + > + if (i2c->irq <= 0) > + return 0; > + > + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, adp5585_irq, > + IRQF_ONESHOT, i2c->name, adp5585); > + if (ret) > + return ret; > + > + /* > + * Clear any possible outstanding interrupt before enabling them. We do that by reading > + * the status register and writing back the same value. > + */ > + ret = regmap_read(adp5585->regmap, ADP5585_INT_STATUS, &stat); > + if (ret) > + return ret; > + > + ret = regmap_write(adp5585->regmap, ADP5585_INT_STATUS, stat); > + if (ret) > + return ret; > + > + ret = regmap_write(adp5585->regmap, regs->int_en, ADP5585_OVRFLOW_IEN | ADP5585_EVENT_IEN); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(&i2c->dev, adp5585_irq_disable, adp5585); > +} > + > static int adp5585_i2c_probe(struct i2c_client *i2c) > { > struct regmap_config regmap_config; > @@ -271,6 +422,8 @@ static int adp5585_i2c_probe(struct i2c_client *i2c) > "Failed to initialize register map\n"); > > adp5585->dev = &i2c->dev; > + adp5585->irq = i2c->irq; > + BLOCKING_INIT_NOTIFIER_HEAD(&adp5585->event_notifier); > > ret = regmap_read(adp5585->regmap, ADP5585_ID, &id); > if (ret) > @@ -282,29 +435,28 @@ static int adp5585_i2c_probe(struct i2c_client *i2c) > return dev_err_probe(&i2c->dev, -ENODEV, > "Invalid device ID 0x%02x\n", id); > > - /* > - * Enable the internal oscillator, as it's shared between multiple > - * functions. > - * > - * As a future improvement, power consumption could possibly be > - * decreased in some use cases by enabling and disabling the oscillator > - * dynamically based on the needs of the child drivers. > - */ > - ret = regmap_set_bits(adp5585->regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); > + ret = adp5585_parse_fw(adp5585); > if (ret) > return ret; > > - ret = devm_add_action_or_reset(&i2c->dev, adp5585_osc_disable, adp5585); > + ret = adp5585_setup(adp5585); > if (ret) > return ret; > > - return adp5585_add_devices(adp5585); > + ret = adp5585_add_devices(adp5585); > + if (ret) > + return ret; > + > + return adp5585_irq_enable(i2c, adp5585); > } > > static int adp5585_suspend(struct device *dev) > { > struct adp5585_dev *adp5585 = dev_get_drvdata(dev); > > + if (adp5585->irq) > + disable_irq(adp5585->irq); > + > regcache_cache_only(adp5585->regmap, true); > > return 0; > @@ -313,11 +465,19 @@ static int adp5585_suspend(struct device *dev) > static int adp5585_resume(struct device *dev) > { > struct adp5585_dev *adp5585 = dev_get_drvdata(dev); > + int ret; > > regcache_cache_only(adp5585->regmap, false); > regcache_mark_dirty(adp5585->regmap); > > - return regcache_sync(adp5585->regmap); > + ret = regcache_sync(adp5585->regmap); > + if (ret) > + return ret; > + > + if (adp5585->irq) > + enable_irq(adp5585->irq); > + > + return 0; > } > > static DEFINE_SIMPLE_DEV_PM_OPS(adp5585_pm, adp5585_suspend, adp5585_resume); > diff --git a/include/linux/mfd/adp5585.h b/include/linux/mfd/adp5585.h > index 2a2bccccaa6ca5cba8ff5716c0d3b82d9541432c..b6baf87907a567fe975f8b24f3c36753e6145066 100644 > --- a/include/linux/mfd/adp5585.h > +++ b/include/linux/mfd/adp5585.h > @@ -10,13 +10,20 @@ > #define __MFD_ADP5585_H_ > > #include <linux/bits.h> > +#include <linux/notifier.h> > > #define ADP5585_ID 0x00 > #define ADP5585_MAN_ID_VALUE 0x20 > #define ADP5585_MAN_ID_MASK GENMASK(7, 4) > +#define ADP5585_REV_ID_MASK GENMASK(3, 0) > #define ADP5585_INT_STATUS 0x01 > +#define ADP5585_OVRFLOW_INT BIT(2) > +#define ADP5585_EVENT_INT BIT(0) > #define ADP5585_STATUS 0x02 > +#define ADP5585_EC_MASK GENMASK(4, 0) > #define ADP5585_FIFO_1 0x03 > +#define ADP5585_KEV_EV_PRESS_MASK BIT(7) > +#define ADP5585_KEY_EVENT_MASK GENMASK(6, 0) > #define ADP5585_FIFO_2 0x04 > #define ADP5585_FIFO_3 0x05 > #define ADP5585_FIFO_4 0x06 > @@ -32,6 +39,7 @@ > #define ADP5585_FIFO_14 0x10 > #define ADP5585_FIFO_15 0x11 > #define ADP5585_FIFO_16 0x12 > +#define ADP5585_EV_MAX (ADP5585_FIFO_16 - ADP5585_FIFO_1 + 1) > #define ADP5585_GPI_INT_STAT_A 0x13 > #define ADP5585_GPI_INT_STAT_B 0x14 > #define ADP5585_GPI_STATUS_A 0x15 > @@ -104,6 +112,8 @@ > #define ADP5585_INT_CFG BIT(1) > #define ADP5585_RST_CFG BIT(0) > #define ADP5585_INT_EN 0x3c > +#define ADP5585_OVRFLOW_IEN BIT(2) > +#define ADP5585_EVENT_IEN BIT(0) > > #define ADP5585_MAX_REG ADP5585_INT_EN > > @@ -121,7 +131,9 @@ > #define ADP5589_PWM_OFFT_LOW 0x3e > #define ADP5589_PWM_ONT_LOW 0x40 > #define ADP5589_PWM_CFG 0x42 > +#define ADP5589_POLL_PTIME_CFG 0x48 > #define ADP5589_PIN_CONFIG_D 0x4C > +#define ADP5589_GENERAL_CFG 0x4d > #define ADP5589_INT_EN 0x4e > #define ADP5589_MAX_REG ADP5589_INT_EN > > @@ -142,15 +154,21 @@ enum adp5585_variant { > }; > > struct adp5585_regs { > + unsigned int gen_cfg; > unsigned int ext_cfg; > + unsigned int int_en; > + unsigned int poll_ptime_cfg; > }; > > struct adp5585_dev { > const struct adp5585_regs *regs; > struct regmap *regmap; > struct device *dev; > + struct blocking_notifier_head event_notifier; > enum adp5585_variant variant; > unsigned int id; > + int irq; > + unsigned int ev_poll_time; > }; > > #endif > > -- > 2.49.0 > >
diff --git a/drivers/mfd/adp5585.c b/drivers/mfd/adp5585.c index e90d16389732f3d8790eb910acd82be2033eaa7e..dcc09c898dd7990b39e21cb2324fa66ae171a802 100644 --- a/drivers/mfd/adp5585.c +++ b/drivers/mfd/adp5585.c @@ -8,6 +8,7 @@ */ #include <linux/array_size.h> +#include <linux/bitfield.h> #include <linux/device.h> #include <linux/err.h> #include <linux/i2c.h> @@ -166,10 +167,16 @@ static const struct regmap_config adp5589_regmap_config_template = { static const struct adp5585_regs adp5585_regs = { .ext_cfg = ADP5585_PIN_CONFIG_C, + .int_en = ADP5585_INT_EN, + .gen_cfg = ADP5585_GENERAL_CFG, + .poll_ptime_cfg = ADP5585_POLL_PTIME_CFG, }; static const struct adp5585_regs adp5589_regs = { .ext_cfg = ADP5589_PIN_CONFIG_D, + .int_en = ADP5589_INT_EN, + .gen_cfg = ADP5589_GENERAL_CFG, + .poll_ptime_cfg = ADP5589_POLL_PTIME_CFG, }; static int adp5585_fill_variant_config(struct adp5585_dev *adp5585, @@ -244,6 +251,150 @@ static void adp5585_osc_disable(void *data) regmap_write(adp5585->regmap, ADP5585_GENERAL_CFG, 0); } +static void adp5585_report_events(struct adp5585_dev *adp5585, int ev_cnt) +{ + unsigned int i; + + for (i = 0; i < ev_cnt; i++) { + unsigned long key_val, key_press; + unsigned int key; + int ret; + + ret = regmap_read(adp5585->regmap, ADP5585_FIFO_1 + i, &key); + if (ret) + return; + + key_val = FIELD_GET(ADP5585_KEY_EVENT_MASK, key); + key_press = FIELD_GET(ADP5585_KEV_EV_PRESS_MASK, key); + + blocking_notifier_call_chain(&adp5585->event_notifier, key_val, (void *)key_press); + } +} + +static irqreturn_t adp5585_irq(int irq, void *data) +{ + struct adp5585_dev *adp5585 = data; + unsigned int status, ev_cnt; + int ret; + + ret = regmap_read(adp5585->regmap, ADP5585_INT_STATUS, &status); + if (ret) + return IRQ_HANDLED; + + if (status & ADP5585_OVRFLOW_INT) + dev_err_ratelimited(adp5585->dev, "Event overflow error\n"); + + if (!(status & ADP5585_EVENT_INT)) + goto out_irq; + + ret = regmap_read(adp5585->regmap, ADP5585_STATUS, &ev_cnt); + if (ret) + goto out_irq; + + ev_cnt = FIELD_GET(ADP5585_EC_MASK, ev_cnt); + if (!ev_cnt) + goto out_irq; + + adp5585_report_events(adp5585, ev_cnt); +out_irq: + regmap_write(adp5585->regmap, ADP5585_INT_STATUS, status); + return IRQ_HANDLED; +} + +static int adp5585_setup(struct adp5585_dev *adp5585) +{ + const struct adp5585_regs *regs = adp5585->regs; + unsigned int reg_val, i; + int ret; + + /* Clear any possible event by reading all the FIFO entries */ + for (i = 0; i < ADP5585_EV_MAX; i++) { + ret = regmap_read(adp5585->regmap, ADP5585_FIFO_1 + i, ®_val); + if (ret) + return ret; + } + + ret = regmap_write(adp5585->regmap, regs->poll_ptime_cfg, adp5585->ev_poll_time); + if (ret) + return ret; + + /* + * Enable the internal oscillator, as it's shared between multiple + * functions. + * + * As a future improvement, power consumption could possibly be + * decreased in some use cases by enabling and disabling the oscillator + * dynamically based on the needs of the child drivers. + */ + ret = regmap_write(adp5585->regmap, regs->gen_cfg, + ADP5585_OSC_FREQ_500KHZ | ADP5585_INT_CFG | ADP5585_OSC_EN); + if (ret) + return ret; + + return devm_add_action_or_reset(adp5585->dev, adp5585_osc_disable, adp5585); +} + +static int adp5585_parse_fw(struct adp5585_dev *adp5585) +{ + unsigned int prop_val; + int ret; + + ret = device_property_read_u32(adp5585->dev, "poll-interval", &prop_val); + if (!ret) { + adp5585->ev_poll_time = prop_val / 10 - 1; + /* + * ev_poll_time is the raw value to be written on the register and 0 to 3 are the + * valid values. + */ + if (adp5585->ev_poll_time > 3) + return dev_err_probe(adp5585->dev, -EINVAL, + "Invalid value(%u) for poll-interval\n", prop_val); + } + + return 0; +} + +static void adp5585_irq_disable(void *data) +{ + struct adp5585_dev *adp5585 = data; + + regmap_write(adp5585->regmap, adp5585->regs->int_en, 0); +} + +static int adp5585_irq_enable(struct i2c_client *i2c, + struct adp5585_dev *adp5585) +{ + const struct adp5585_regs *regs = adp5585->regs; + unsigned int stat; + int ret; + + if (i2c->irq <= 0) + return 0; + + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, adp5585_irq, + IRQF_ONESHOT, i2c->name, adp5585); + if (ret) + return ret; + + /* + * Clear any possible outstanding interrupt before enabling them. We do that by reading + * the status register and writing back the same value. + */ + ret = regmap_read(adp5585->regmap, ADP5585_INT_STATUS, &stat); + if (ret) + return ret; + + ret = regmap_write(adp5585->regmap, ADP5585_INT_STATUS, stat); + if (ret) + return ret; + + ret = regmap_write(adp5585->regmap, regs->int_en, ADP5585_OVRFLOW_IEN | ADP5585_EVENT_IEN); + if (ret) + return ret; + + return devm_add_action_or_reset(&i2c->dev, adp5585_irq_disable, adp5585); +} + static int adp5585_i2c_probe(struct i2c_client *i2c) { struct regmap_config regmap_config; @@ -271,6 +422,8 @@ static int adp5585_i2c_probe(struct i2c_client *i2c) "Failed to initialize register map\n"); adp5585->dev = &i2c->dev; + adp5585->irq = i2c->irq; + BLOCKING_INIT_NOTIFIER_HEAD(&adp5585->event_notifier); ret = regmap_read(adp5585->regmap, ADP5585_ID, &id); if (ret) @@ -282,29 +435,28 @@ static int adp5585_i2c_probe(struct i2c_client *i2c) return dev_err_probe(&i2c->dev, -ENODEV, "Invalid device ID 0x%02x\n", id); - /* - * Enable the internal oscillator, as it's shared between multiple - * functions. - * - * As a future improvement, power consumption could possibly be - * decreased in some use cases by enabling and disabling the oscillator - * dynamically based on the needs of the child drivers. - */ - ret = regmap_set_bits(adp5585->regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); + ret = adp5585_parse_fw(adp5585); if (ret) return ret; - ret = devm_add_action_or_reset(&i2c->dev, adp5585_osc_disable, adp5585); + ret = adp5585_setup(adp5585); if (ret) return ret; - return adp5585_add_devices(adp5585); + ret = adp5585_add_devices(adp5585); + if (ret) + return ret; + + return adp5585_irq_enable(i2c, adp5585); } static int adp5585_suspend(struct device *dev) { struct adp5585_dev *adp5585 = dev_get_drvdata(dev); + if (adp5585->irq) + disable_irq(adp5585->irq); + regcache_cache_only(adp5585->regmap, true); return 0; @@ -313,11 +465,19 @@ static int adp5585_suspend(struct device *dev) static int adp5585_resume(struct device *dev) { struct adp5585_dev *adp5585 = dev_get_drvdata(dev); + int ret; regcache_cache_only(adp5585->regmap, false); regcache_mark_dirty(adp5585->regmap); - return regcache_sync(adp5585->regmap); + ret = regcache_sync(adp5585->regmap); + if (ret) + return ret; + + if (adp5585->irq) + enable_irq(adp5585->irq); + + return 0; } static DEFINE_SIMPLE_DEV_PM_OPS(adp5585_pm, adp5585_suspend, adp5585_resume); diff --git a/include/linux/mfd/adp5585.h b/include/linux/mfd/adp5585.h index 2a2bccccaa6ca5cba8ff5716c0d3b82d9541432c..b6baf87907a567fe975f8b24f3c36753e6145066 100644 --- a/include/linux/mfd/adp5585.h +++ b/include/linux/mfd/adp5585.h @@ -10,13 +10,20 @@ #define __MFD_ADP5585_H_ #include <linux/bits.h> +#include <linux/notifier.h> #define ADP5585_ID 0x00 #define ADP5585_MAN_ID_VALUE 0x20 #define ADP5585_MAN_ID_MASK GENMASK(7, 4) +#define ADP5585_REV_ID_MASK GENMASK(3, 0) #define ADP5585_INT_STATUS 0x01 +#define ADP5585_OVRFLOW_INT BIT(2) +#define ADP5585_EVENT_INT BIT(0) #define ADP5585_STATUS 0x02 +#define ADP5585_EC_MASK GENMASK(4, 0) #define ADP5585_FIFO_1 0x03 +#define ADP5585_KEV_EV_PRESS_MASK BIT(7) +#define ADP5585_KEY_EVENT_MASK GENMASK(6, 0) #define ADP5585_FIFO_2 0x04 #define ADP5585_FIFO_3 0x05 #define ADP5585_FIFO_4 0x06 @@ -32,6 +39,7 @@ #define ADP5585_FIFO_14 0x10 #define ADP5585_FIFO_15 0x11 #define ADP5585_FIFO_16 0x12 +#define ADP5585_EV_MAX (ADP5585_FIFO_16 - ADP5585_FIFO_1 + 1) #define ADP5585_GPI_INT_STAT_A 0x13 #define ADP5585_GPI_INT_STAT_B 0x14 #define ADP5585_GPI_STATUS_A 0x15 @@ -104,6 +112,8 @@ #define ADP5585_INT_CFG BIT(1) #define ADP5585_RST_CFG BIT(0) #define ADP5585_INT_EN 0x3c +#define ADP5585_OVRFLOW_IEN BIT(2) +#define ADP5585_EVENT_IEN BIT(0) #define ADP5585_MAX_REG ADP5585_INT_EN @@ -121,7 +131,9 @@ #define ADP5589_PWM_OFFT_LOW 0x3e #define ADP5589_PWM_ONT_LOW 0x40 #define ADP5589_PWM_CFG 0x42 +#define ADP5589_POLL_PTIME_CFG 0x48 #define ADP5589_PIN_CONFIG_D 0x4C +#define ADP5589_GENERAL_CFG 0x4d #define ADP5589_INT_EN 0x4e #define ADP5589_MAX_REG ADP5589_INT_EN @@ -142,15 +154,21 @@ enum adp5585_variant { }; struct adp5585_regs { + unsigned int gen_cfg; unsigned int ext_cfg; + unsigned int int_en; + unsigned int poll_ptime_cfg; }; struct adp5585_dev { const struct adp5585_regs *regs; struct regmap *regmap; struct device *dev; + struct blocking_notifier_head event_notifier; enum adp5585_variant variant; unsigned int id; + int irq; + unsigned int ev_poll_time; }; #endif