diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-04-08 05:48:52 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-04-08 05:48:52 +0300 |
commit | 8645f09bad14df3776484b44933a41c446343087 (patch) | |
tree | 527f2b915378e8dd9d23e79544d73de79b09700b /drivers | |
parent | b574beb6257a21c231a6c2c272a0aeb84b655437 (diff) | |
parent | d2923aa4535664ae5c46c3a093985afd18fec118 (diff) | |
download | linux-8645f09bad14df3776484b44933a41c446343087.tar.xz |
Merge tag 'mfd-next-5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Pull mfd updates from Lee Jones:
"New Drivers:
- Add support for IQS620A/621/622/624/625 Azoteq IQS62X Sensors
New Device Support:
- Add support for ADC, IRQ, Regulator, RTC and WDT to Ricoh RN5T618 PMIC
- Add support for Comet Lake to Intel LPSS
New Functionality:
- Add support for Charger Detection to Spreadtrum SC27xx PMICs
- Add support for Interrupt Polarity to Dialog Semi DA9062/61 PMIC
- Add ACPI enumeration support to Diolan DLN2 USB Adaptor
Fix-ups:
- Device Tree; iqs62x, rn5t618, cros_ec_dev, stm32-lptimer, rohm,bd71837, rohm,bd71847
- I2C registration; rn5t618
- Kconfig; MFD_CPCAP, AB8500_CORE, MFD_WM8994, MFD_WM97xx, MFD_STPMIC1
- Use flexible-array members; omap-usb-tll, qcom-pm8xxx
- Remove unnecessary casts; omap-usb-host, omap-usb-tll
- Power (suspend/resume/poweroff) enhancements; rk808
- Improve error/sanity checking; dln2
- Use snprintf(); aat2870-core
Bug Fixes:
- Fix PCI IDs in intel-lpss-pci"
* tag 'mfd-next-5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (33 commits)
mfd: intel-lpss: Fix Intel Elkhart Lake LPSS I2C input clock
mfd: aat2870: Use scnprintf() for avoiding potential buffer overflow
mfd: dln2: Allow to be enumerated via ACPI
mfd: da9062: Add support for interrupt polarity defined in device tree
dt-bindings: bd718x7: Yamlify and add BD71850
mfd: dln2: Fix sanity checking for endpoints
mfd: intel-lpss: Add Intel Comet Lake PCH-V PCI IDs
mfd: sc27xx: Add USB charger type detection support
dt-bindings: mfd: Document STM32 low power timer bindings
mfd: rk808: Convert RK805 to shutdown/suspend hooks
mfd: rk808: Reduce shutdown duplication
mfd: rk808: Stop using syscore ops
mfd: rk808: Ensure suspend/resume hooks always work
mfd: rk808: Always use poweroff when requested
mfd: omap: Remove useless cast for driver.name
mfd: Kconfig: Fix some misspelling of the word functionality
mfd: pm8xxx: Replace zero-length array with flexible-array member
mfd: omap-usb-tll: Replace zero-length array with flexible-array member
mfd: cpcap: Fix compile if MFD_CORE is not selected
mfd: cros_ec: Check DT node for usbpd-notify add
...
Diffstat (limited to 'drivers')
34 files changed, 3495 insertions, 125 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 5bd51853b15e..d5c073a8aa3e 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -88,6 +88,7 @@ source "drivers/iio/orientation/Kconfig" if IIO_TRIGGER source "drivers/iio/trigger/Kconfig" endif #IIO_TRIGGER +source "drivers/iio/position/Kconfig" source "drivers/iio/potentiometer/Kconfig" source "drivers/iio/potentiostat/Kconfig" source "drivers/iio/pressure/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index bff682ad1cfb..1712011c0f4a 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -31,6 +31,7 @@ obj-y += light/ obj-y += magnetometer/ obj-y += multiplexer/ obj-y += orientation/ +obj-y += position/ obj-y += potentiometer/ obj-y += potentiostat/ obj-y += pressure/ diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index f4da821c4022..12bb8b7ca1ff 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -795,6 +795,16 @@ config RCAR_GYRO_ADC To compile this driver as a module, choose M here: the module will be called rcar-gyroadc. +config RN5T618_ADC + tristate "ADC for the RN5T618/RC5T619 family of chips" + depends on MFD_RN5T618 + help + Say yes here to build support for the integrated ADC inside the + RN5T618/619 series PMICs: + + This driver can also be built as a module. If so, the module + will be called rn5t618-adc. + config ROCKCHIP_SARADC tristate "Rockchip SARADC driver" depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST) diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 8462455b4228..637807861112 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-pm8xxx-xoadc.o obj-$(CONFIG_RCAR_GYRO_ADC) += rcar-gyroadc.o +obj-$(CONFIG_RN5T618_ADC) += rn5t618-adc.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_SC27XX_ADC) += sc27xx_adc.o obj-$(CONFIG_SPEAR_ADC) += spear_adc.o diff --git a/drivers/iio/adc/rn5t618-adc.c b/drivers/iio/adc/rn5t618-adc.c new file mode 100644 index 000000000000..f21027e4e26a --- /dev/null +++ b/drivers/iio/adc/rn5t618-adc.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADC driver for the RICOH RN5T618 power management chip family + * + * Copyright (C) 2019 Andreas Kemnade + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mfd/rn5t618.h> +#include <linux/platform_device.h> +#include <linux/completion.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/slab.h> + +#define RN5T618_ADC_CONVERSION_TIMEOUT (msecs_to_jiffies(500)) +#define RN5T618_REFERENCE_VOLT 2500 + +/* mask for selecting channels for single conversion */ +#define RN5T618_ADCCNT3_CHANNEL_MASK 0x7 +/* average 4-time conversion mode */ +#define RN5T618_ADCCNT3_AVG BIT(3) +/* set for starting a single conversion, gets cleared by hw when done */ +#define RN5T618_ADCCNT3_GODONE BIT(4) +/* automatic conversion, period is in ADCCNT2, selected channels are + * in ADCCNT1 + */ +#define RN5T618_ADCCNT3_AUTO BIT(5) +#define RN5T618_ADCEND_IRQ BIT(0) + +struct rn5t618_adc_data { + struct device *dev; + struct rn5t618 *rn5t618; + struct completion conv_completion; + int irq; +}; + +struct rn5t618_channel_ratios { + u16 numerator; + u16 denominator; +}; + +enum rn5t618_channels { + LIMMON = 0, + VBAT, + VADP, + VUSB, + VSYS, + VTHM, + AIN1, + AIN0 +}; + +static const struct rn5t618_channel_ratios rn5t618_ratios[8] = { + [LIMMON] = {50, 32}, /* measured across 20mOhm, amplified by 32 */ + [VBAT] = {2, 1}, + [VADP] = {3, 1}, + [VUSB] = {3, 1}, + [VSYS] = {3, 1}, + [VTHM] = {1, 1}, + [AIN1] = {1, 1}, + [AIN0] = {1, 1}, +}; + +static int rn5t618_read_adc_reg(struct rn5t618 *rn5t618, int reg, u16 *val) +{ + u8 data[2]; + int ret; + + ret = regmap_bulk_read(rn5t618->regmap, reg, data, sizeof(data)); + if (ret < 0) + return ret; + + *val = (data[0] << 4) | (data[1] & 0xF); + + return 0; +} + +static irqreturn_t rn5t618_adc_irq(int irq, void *data) +{ + struct rn5t618_adc_data *adc = data; + unsigned int r = 0; + int ret; + + /* clear low & high threshold irqs */ + regmap_write(adc->rn5t618->regmap, RN5T618_IR_ADC1, 0); + regmap_write(adc->rn5t618->regmap, RN5T618_IR_ADC2, 0); + + ret = regmap_read(adc->rn5t618->regmap, RN5T618_IR_ADC3, &r); + if (ret < 0) + dev_err(adc->dev, "failed to read IRQ status: %d\n", ret); + + regmap_write(adc->rn5t618->regmap, RN5T618_IR_ADC3, 0); + + if (r & RN5T618_ADCEND_IRQ) + complete(&adc->conv_completion); + + return IRQ_HANDLED; +} + +static int rn5t618_adc_read(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct rn5t618_adc_data *adc = iio_priv(iio_dev); + u16 raw; + int ret; + + if (mask == IIO_CHAN_INFO_SCALE) { + *val = RN5T618_REFERENCE_VOLT * + rn5t618_ratios[chan->channel].numerator; + *val2 = rn5t618_ratios[chan->channel].denominator * 4095; + + return IIO_VAL_FRACTIONAL; + } + + /* select channel */ + ret = regmap_update_bits(adc->rn5t618->regmap, RN5T618_ADCCNT3, + RN5T618_ADCCNT3_CHANNEL_MASK, + chan->channel); + if (ret < 0) + return ret; + + ret = regmap_write(adc->rn5t618->regmap, RN5T618_EN_ADCIR3, + RN5T618_ADCEND_IRQ); + if (ret < 0) + return ret; + + ret = regmap_update_bits(adc->rn5t618->regmap, RN5T618_ADCCNT3, + RN5T618_ADCCNT3_AVG, + mask == IIO_CHAN_INFO_AVERAGE_RAW ? + RN5T618_ADCCNT3_AVG : 0); + if (ret < 0) + return ret; + + init_completion(&adc->conv_completion); + /* single conversion */ + ret = regmap_update_bits(adc->rn5t618->regmap, RN5T618_ADCCNT3, + RN5T618_ADCCNT3_GODONE, + RN5T618_ADCCNT3_GODONE); + if (ret < 0) + return ret; + + ret = wait_for_completion_timeout(&adc->conv_completion, + RN5T618_ADC_CONVERSION_TIMEOUT); + if (ret == 0) { + dev_warn(adc->dev, "timeout waiting for adc result\n"); + return -ETIMEDOUT; + } + + ret = rn5t618_read_adc_reg(adc->rn5t618, + RN5T618_ILIMDATAH + 2 * chan->channel, + &raw); + if (ret < 0) + return ret; + + *val = raw; + + return IIO_VAL_INT; +} + +static const struct iio_info rn5t618_adc_iio_info = { + .read_raw = &rn5t618_adc_read, +}; + +#define RN5T618_ADC_CHANNEL(_channel, _type, _name) { \ + .type = _type, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _name, \ + .indexed = 1. \ +} + +static const struct iio_chan_spec rn5t618_adc_iio_channels[] = { + RN5T618_ADC_CHANNEL(LIMMON, IIO_CURRENT, "LIMMON"), + RN5T618_ADC_CHANNEL(VBAT, IIO_VOLTAGE, "VBAT"), + RN5T618_ADC_CHANNEL(VADP, IIO_VOLTAGE, "VADP"), + RN5T618_ADC_CHANNEL(VUSB, IIO_VOLTAGE, "VUSB"), + RN5T618_ADC_CHANNEL(VSYS, IIO_VOLTAGE, "VSYS"), + RN5T618_ADC_CHANNEL(VTHM, IIO_VOLTAGE, "VTHM"), + RN5T618_ADC_CHANNEL(AIN1, IIO_VOLTAGE, "AIN1"), + RN5T618_ADC_CHANNEL(AIN0, IIO_VOLTAGE, "AIN0") +}; + +static int rn5t618_adc_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *iio_dev; + struct rn5t618_adc_data *adc; + struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent); + + iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!iio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + adc = iio_priv(iio_dev); + adc->dev = &pdev->dev; + adc->rn5t618 = rn5t618; + + if (rn5t618->irq_data) + adc->irq = regmap_irq_get_virq(rn5t618->irq_data, + RN5T618_IRQ_ADC); + + if (adc->irq <= 0) { + dev_err(&pdev->dev, "get virq failed\n"); + return -EINVAL; + } + + init_completion(&adc->conv_completion); + + iio_dev->name = dev_name(&pdev->dev); + iio_dev->dev.parent = &pdev->dev; + iio_dev->info = &rn5t618_adc_iio_info; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = rn5t618_adc_iio_channels; + iio_dev->num_channels = ARRAY_SIZE(rn5t618_adc_iio_channels); + + /* stop any auto-conversion */ + ret = regmap_write(rn5t618->regmap, RN5T618_ADCCNT3, 0); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, iio_dev); + + ret = devm_request_threaded_irq(adc->dev, adc->irq, NULL, + rn5t618_adc_irq, + IRQF_ONESHOT, dev_name(adc->dev), + adc); + if (ret < 0) { + dev_err(adc->dev, "request irq %d failed: %d\n", adc->irq, ret); + return ret; + } + + return devm_iio_device_register(adc->dev, iio_dev); +} + +static struct platform_driver rn5t618_adc_driver = { + .driver = { + .name = "rn5t618-adc", + }, + .probe = rn5t618_adc_probe, +}; + +module_platform_driver(rn5t618_adc_driver); +MODULE_ALIAS("platform:rn5t618-adc"); +MODULE_DESCRIPTION("RICOH RN5T618 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 74970f18a93b..b27719cefcf9 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -194,6 +194,16 @@ config GP2AP020A00F To compile this driver as a module, choose M here: the module will be called gp2ap020a00f. +config IQS621_ALS + tristate "Azoteq IQS621/622 ambient light sensors" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS621 + and IQS622 ambient light sensors. + + To compile this driver as a module, choose M here: the module + will be called iqs621-als. + config SENSORS_ISL29018 tristate "Intersil 29018 light and proximity sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 5c1ebaf578fb..d1c8aa30b9a8 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_GP2AP002) += gp2ap002.o obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o +obj-$(CONFIG_IQS621_ALS) += iqs621-als.o obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o obj-$(CONFIG_ISL29125) += isl29125.o diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c new file mode 100644 index 000000000000..b2988a782bd0 --- /dev/null +++ b/drivers/iio/light/iqs621-als.c @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS621/622 Ambient Light Sensors + * + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> + */ + +#include <linux/device.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/kernel.h> +#include <linux/mfd/iqs62x.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define IQS621_ALS_FLAGS_LIGHT BIT(7) +#define IQS621_ALS_FLAGS_RANGE GENMASK(3, 0) + +#define IQS621_ALS_UI_OUT 0x17 + +#define IQS621_ALS_THRESH_DARK 0x80 +#define IQS621_ALS_THRESH_LIGHT 0x81 + +#define IQS622_IR_RANGE 0x15 +#define IQS622_IR_FLAGS 0x16 +#define IQS622_IR_FLAGS_TOUCH BIT(1) +#define IQS622_IR_FLAGS_PROX BIT(0) + +#define IQS622_IR_UI_OUT 0x17 + +#define IQS622_IR_THRESH_PROX 0x91 +#define IQS622_IR_THRESH_TOUCH 0x92 + +struct iqs621_als_private { + struct iqs62x_core *iqs62x; + struct notifier_block notifier; + struct mutex lock; + bool light_en; + bool range_en; + bool prox_en; + u8 als_flags; + u8 ir_flags_mask; + u8 ir_flags; + u8 thresh_light; + u8 thresh_dark; + u8 thresh_prox; +}; + +static int iqs621_als_init(struct iqs621_als_private *iqs621_als) +{ + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int event_mask = 0; + int ret; + + switch (iqs621_als->ir_flags_mask) { + case IQS622_IR_FLAGS_TOUCH: + ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_TOUCH, + iqs621_als->thresh_prox); + break; + + case IQS622_IR_FLAGS_PROX: + ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_PROX, + iqs621_als->thresh_prox); + break; + + default: + ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT, + iqs621_als->thresh_light); + if (ret) + return ret; + + ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_DARK, + iqs621_als->thresh_dark); + } + + if (ret) + return ret; + + if (iqs621_als->light_en || iqs621_als->range_en) + event_mask |= iqs62x->dev_desc->als_mask; + + if (iqs621_als->prox_en) + event_mask |= iqs62x->dev_desc->ir_mask; + + return regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + event_mask, 0); +} + +static int iqs621_als_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + bool light_new, light_old; + bool prox_new, prox_old; + u8 range_new, range_old; + s64 timestamp; + int ret; + + iqs621_als = container_of(notifier, struct iqs621_als_private, + notifier); + indio_dev = iio_priv_to_dev(iqs621_als); + timestamp = iio_get_time_ns(indio_dev); + + mutex_lock(&iqs621_als->lock); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs621_als_init(iqs621_als); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", ret); + ret = NOTIFY_BAD; + } else { + ret = NOTIFY_OK; + } + + goto err_mutex; + } + + if (!iqs621_als->light_en && !iqs621_als->range_en && + !iqs621_als->prox_en) { + ret = NOTIFY_DONE; + goto err_mutex; + } + + /* IQS621 only */ + light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT; + light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT; + + if (iqs621_als->light_en && light_new && !light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->light_en && !light_new && light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS621 and IQS622 */ + range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE; + range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE; + + if (iqs621_als->range_en && (range_new > range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->range_en && (range_new < range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS622 only */ + prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask; + prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask; + + if (iqs621_als->prox_en && prox_new && !prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->prox_en && !prox_new && prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + iqs621_als->als_flags = event_data->als_flags; + iqs621_als->ir_flags = event_data->ir_flags; + ret = NOTIFY_OK; + +err_mutex: + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static void iqs621_als_notifier_unregister(void *context) +{ + struct iqs621_als_private *iqs621_als = context; + struct iio_dev *indio_dev = iio_priv_to_dev(iqs621_als); + int ret; + + ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs621_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + int ret; + __le16 val_buf; + + switch (chan->type) { + case IIO_INTENSITY: + ret = regmap_read(iqs62x->regmap, chan->address, val); + if (ret) + return ret; + + *val &= IQS621_ALS_FLAGS_RANGE; + return IIO_VAL_INT; + + case IIO_PROXIMITY: + case IIO_LIGHT: + ret = regmap_raw_read(iqs62x->regmap, chan->address, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int iqs621_als_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + int ret; + + mutex_lock(&iqs621_als->lock); + + switch (chan->type) { + case IIO_LIGHT: + ret = iqs621_als->light_en; + break; + + case IIO_INTENSITY: + ret = iqs621_als->range_en; + break; + + case IIO_PROXIMITY: + ret = iqs621_als->prox_en; + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static int iqs621_als_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int val; + int ret; + + mutex_lock(&iqs621_als->lock); + + ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->als_flags, &val); + if (ret) + goto err_mutex; + iqs621_als->als_flags = val; + + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->range_en || state ? 0 : + 0xFF); + if (!ret) + iqs621_als->light_en = state; + break; + + case IIO_INTENSITY: + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->light_en || state ? 0 : + 0xFF); + if (!ret) + iqs621_als->range_en = state; + break; + + case IIO_PROXIMITY: + ret = regmap_read(iqs62x->regmap, IQS622_IR_FLAGS, &val); + if (ret) + goto err_mutex; + iqs621_als->ir_flags = val; + + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->ir_mask, + state ? 0 : 0xFF); + if (!ret) + iqs621_als->prox_en = state; + break; + + default: + ret = -EINVAL; + } + +err_mutex: + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static int iqs621_als_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + int ret = IIO_VAL_INT; + + mutex_lock(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = iqs621_als->thresh_light * 16; + break; + + case IIO_EV_DIR_FALLING: + *val = iqs621_als->thresh_dark * 4; + break; + + case IIO_EV_DIR_EITHER: + if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH) + *val = iqs621_als->thresh_prox * 4; + else + *val = iqs621_als->thresh_prox; + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static int iqs621_als_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int thresh_reg, thresh_val; + u8 ir_flags_mask, *thresh_cache; + int ret = -EINVAL; + + mutex_lock(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + thresh_reg = IQS621_ALS_THRESH_LIGHT; + thresh_val = val / 16; + + thresh_cache = &iqs621_als->thresh_light; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_FALLING: + thresh_reg = IQS621_ALS_THRESH_DARK; + thresh_val = val / 4; + + thresh_cache = &iqs621_als->thresh_dark; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_EITHER: + /* + * The IQS622 supports two detection thresholds, both measured + * in the same arbitrary units reported by read_raw: proximity + * (0 through 255 in steps of 1), and touch (0 through 1020 in + * steps of 4). + * + * Based on the single detection threshold chosen by the user, + * select the hardware threshold that gives the best trade-off + * between range and resolution. + * + * By default, the close-range (but coarse) touch threshold is + * chosen during probe. + */ + switch (val) { + case 0 ... 255: + thresh_reg = IQS622_IR_THRESH_PROX; + thresh_val = val; + + ir_flags_mask = IQS622_IR_FLAGS_PROX; + break; + + case 256 ... 1020: + thresh_reg = IQS622_IR_THRESH_TOUCH; + thresh_val = val / 4; + + ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + break; + + default: + goto err_mutex; + } + + thresh_cache = &iqs621_als->thresh_prox; + break; + + default: + goto err_mutex; + } + + if (thresh_val > 0xFF) + goto err_mutex; + + ret = regmap_write(iqs62x->regmap, thresh_reg, thresh_val); + if (ret) + goto err_mutex; + + *thresh_cache = thresh_val; + iqs621_als->ir_flags_mask = ir_flags_mask; + +err_mutex: + mutex_unlock(&iqs621_als->lock); + + return ret; +} + +static const struct iio_info iqs621_als_info = { + .read_raw = &iqs621_als_read_raw, + .read_event_config = iqs621_als_read_event_config, + .write_event_config = iqs621_als_write_event_config, + .read_event_value = iqs621_als_read_event_value, + .write_event_value = iqs621_als_write_event_value, +}; + +static const struct iio_event_spec iqs621_als_range_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec iqs621_als_light_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs621_als_channels[] = { + { + .type = IIO_INTENSITY, + .address = IQS621_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + }, + { + .type = IIO_LIGHT, + .address = IQS621_ALS_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .event_spec = iqs621_als_light_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_light_events), + }, +}; + +static const struct iio_event_spec iqs622_als_prox_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs622_als_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_BOTH, + .address = IQS622_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + .modified = true, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .address = IQS622_IR_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = true, + }, + { + .type = IIO_PROXIMITY, + .address = IQS622_IR_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs622_als_prox_events, + .num_event_specs = ARRAY_SIZE(iqs622_als_prox_events), + }, +}; + +static int iqs621_als_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + unsigned int val; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als)); + if (!indio_dev) + return -ENOMEM; + + iqs621_als = iio_priv(indio_dev); + iqs621_als->iqs62x = iqs62x; + + if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) { + ret = regmap_read(iqs62x->regmap, IQS622_IR_THRESH_TOUCH, + &val); + if (ret) + return ret; + iqs621_als->thresh_prox = val; + iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + + indio_dev->channels = iqs622_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels); + } else { + ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT, + &val); + if (ret) + return ret; + iqs621_als->thresh_light = val; + + ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_DARK, + &val); + if (ret) + return ret; + iqs621_als->thresh_dark = val; + + indio_dev->channels = iqs621_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels); + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs621_als_info; + + mutex_init(&iqs621_als->lock); + + iqs621_als->notifier.notifier_call = iqs621_als_notifier; + ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs621_als_notifier_unregister, + iqs621_als); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs621_als_platform_driver = { + .driver = { + .name = "iqs621-als", + }, + .probe = iqs621_als_probe, +}; +module_platform_driver(iqs621_als_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs621-als"); diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig new file mode 100644 index 000000000000..eda67f008c5b --- /dev/null +++ b/drivers/iio/position/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Linear and angular position sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Linear and angular position sensors" + +config IQS624_POS + tristate "Azoteq IQS624/625 angular position sensors" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS624 + and IQS625 angular position sensors. + + To compile this driver as a module, choose M here: the module + will be called iqs624-pos. + +endmenu diff --git a/drivers/iio/position/Makefile b/drivers/iio/position/Makefile new file mode 100644 index 000000000000..3cbe7a734352 --- /dev/null +++ b/drivers/iio/position/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for IIO linear and angular position sensors +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_IQS624_POS) += iqs624-pos.o diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c new file mode 100644 index 000000000000..77096c31c2ba --- /dev/null +++ b/drivers/iio/position/iqs624-pos.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS624/625 Angular Position Sensors + * + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> + */ + +#include <linux/device.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/kernel.h> +#include <linux/mfd/iqs62x.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define IQS624_POS_DEG_OUT 0x16 + +#define IQS624_POS_SCALE1 (314159 / 180) +#define IQS624_POS_SCALE2 100000 + +struct iqs624_pos_private { + struct iqs62x_core *iqs62x; + struct notifier_block notifier; + struct mutex lock; + bool angle_en; + u16 angle; +}; + +static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en) +{ + unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT; + + /* + * The IQS625 reports angular position in the form of coarse intervals, + * so only interval change events are unmasked. Conversely, the IQS624 + * reports angular position down to one degree of resolution, so wheel + * movement events are unmasked instead. + */ + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + event_mask = IQS624_HALL_UI_INT_EVENT; + + return regmap_update_bits(iqs62x->regmap, IQS624_HALL_UI, event_mask, + angle_en ? 0 : 0xFF); +} + +static int iqs624_pos_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs624_pos_private *iqs624_pos; + struct iqs62x_core *iqs62x; + struct iio_dev *indio_dev; + u16 angle = event_data->ui_data; + s64 timestamp; + int ret; + + iqs624_pos = container_of(notifier, struct iqs624_pos_private, + notifier); + indio_dev = iio_priv_to_dev(iqs624_pos); + timestamp = iio_get_time_ns(indio_dev); + + iqs62x = iqs624_pos->iqs62x; + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + angle = event_data->interval; + + mutex_lock(&iqs624_pos->lock); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", ret); + ret = NOTIFY_BAD; + } else { + ret = NOTIFY_OK; + } + } else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + iqs624_pos->angle = angle; + ret = NOTIFY_OK; + } else { + ret = NOTIFY_DONE; + } + + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static void iqs624_pos_notifier_unregister(void *context) +{ + struct iqs624_pos_private *iqs624_pos = context; + struct iio_dev *indio_dev = iio_priv_to_dev(iqs624_pos); + int ret; + + ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (ret) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val) +{ + int ret; + __le16 val_buf; + + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + return regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval, + val); + + ret = regmap_raw_read(iqs62x->regmap, IQS624_POS_DEG_OUT, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + + return 0; +} + +static int iqs624_pos_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; + unsigned int scale = 1; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iqs624_pos_angle_get(iqs62x, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) { + ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, + &scale); + if (ret) + return ret; + } + + *val = scale * IQS624_POS_SCALE1; + *val2 = IQS624_POS_SCALE2; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } +} + +static int iqs624_pos_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + int ret; + + mutex_lock(&iqs624_pos->lock); + ret = iqs624_pos->angle_en; + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static int iqs624_pos_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; + unsigned int val; + int ret; + + mutex_lock(&iqs624_pos->lock); + + ret = iqs624_pos_angle_get(iqs62x, &val); + if (ret) + goto err_mutex; + + ret = iqs624_pos_angle_en(iqs62x, state); + if (ret) + goto err_mutex; + + iqs624_pos->angle = val; + iqs624_pos->angle_en = state; + +err_mutex: + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static const struct iio_info iqs624_pos_info = { + .read_raw = &iqs624_pos_read_raw, + .read_event_config = iqs624_pos_read_event_config, + .write_event_config = iqs624_pos_write_event_config, +}; + +static const struct iio_event_spec iqs624_pos_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec iqs624_pos_channels[] = { + { + .type = IIO_ANGL, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = iqs624_pos_events, + .num_event_specs = ARRAY_SIZE(iqs624_pos_events), + }, +}; + +static int iqs624_pos_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs624_pos_private *iqs624_pos; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos)); + if (!indio_dev) + return -ENOMEM; + + iqs624_pos = iio_priv(indio_dev); + iqs624_pos->iqs62x = iqs62x; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->channels = iqs624_pos_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs624_pos_info; + + mutex_init(&iqs624_pos->lock); + + iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; + ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs624_pos_notifier_unregister, + iqs624_pos); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs624_pos_platform_driver = { + .driver = { + .name = "iqs624-pos", + }, + .probe = iqs624_pos_probe, +}; +module_platform_driver(iqs624_pos_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs624-pos"); diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index e1ccb4003015..f1f2a1499c9e 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -4,6 +4,16 @@ # menu "Temperature sensors" +config IQS620AT_TEMP + tristate "Azoteq IQS620AT temperature sensor" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS620AT + temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called iqs620at-temp. + config LTC2983 tristate "Analog Devices Multi-Sensor Digital Temperature Measurement System" depends on SPI diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile index d6b850b0cf63..90c113115422 100644 --- a/drivers/iio/temperature/Makefile +++ b/drivers/iio/temperature/Makefile @@ -3,6 +3,7 @@ # Makefile for industrial I/O temperature drivers # +obj-$(CONFIG_IQS620AT_TEMP) += iqs620at-temp.o obj-$(CONFIG_LTC2983) += ltc2983.o obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c new file mode 100644 index 000000000000..3fd52b3eb030 --- /dev/null +++ b/drivers/iio/temperature/iqs620at-temp.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620AT Temperature Sensor + * + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> + */ + +#include <linux/device.h> +#include <linux/iio/iio.h> +#include <linux/kernel.h> +#include <linux/mfd/iqs62x.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define IQS620_TEMP_UI_OUT 0x1A + +#define IQS620_TEMP_SCALE 1000 +#define IQS620_TEMP_OFFSET (-100) + +static int iqs620_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs62x_core *iqs62x = iio_device_get_drvdata(indio_dev); + int ret; + __le16 val_buf; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_raw_read(iqs62x->regmap, IQS620_TEMP_UI_OUT, + &val_buf, sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = IQS620_TEMP_SCALE; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OFFSET: + *val = IQS620_TEMP_OFFSET; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_info iqs620_temp_info = { + .read_raw = &iqs620_temp_read_raw, +}; + +static const struct iio_chan_spec iqs620_temp_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + }, +}; + +static int iqs620_temp_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&pdev->dev, 0); + if (!indio_dev) + return -ENOMEM; + + iio_device_set_drvdata(indio_dev, iqs62x); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = &pdev->dev; + indio_dev->channels = iqs620_temp_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs620_temp_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs620_temp_info; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs620_temp_platform_driver = { + .driver = { + .name = "iqs620at-temp", + }, + .probe = iqs620_temp_probe, +}; +module_platform_driver(iqs620_temp_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS620AT Temperature Sensor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs620at-temp"); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 4706ff09f0e8..28de965a08d5 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -663,6 +663,16 @@ config KEYBOARD_IPAQ_MICRO To compile this driver as a module, choose M here: the module will be called ipaq-micro-keys. +config KEYBOARD_IQS62X + tristate "Azoteq IQS620A/621/622/624/625 keys and switches" + depends on MFD_IQS62X + help + Say Y here to enable key and switch support for the Azoteq IQS620A, + IQS621, IQS622, IQS624 and IQS625 multi-function sensors. + + To compile this driver as a module, choose M here: the module will + be called iqs62x-keys. + config KEYBOARD_OMAP tristate "TI OMAP keypad support" depends on ARCH_OMAP1 diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index f5b17524adf2..1d689fdd5c00 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_KEYBOARD_TCA8418) += tca8418_keypad.o obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o obj-$(CONFIG_KEYBOARD_IPAQ_MICRO) += ipaq-micro-keys.o +obj-$(CONFIG_KEYBOARD_IQS62X) += iqs62x-keys.o obj-$(CONFIG_KEYBOARD_IMX) += imx_keypad.o obj-$(CONFIG_KEYBOARD_IMX_SC_KEY) += imx_sc_key.o obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o diff --git a/drivers/input/keyboard/iqs62x-keys.c b/drivers/input/keyboard/iqs62x-keys.c new file mode 100644 index 000000000000..93446b21f98f --- /dev/null +++ b/drivers/input/keyboard/iqs62x-keys.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A/621/622/624/625 Keys and Switches + * + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> + */ + +#include <linux/device.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/mfd/iqs62x.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +enum { + IQS62X_SW_HALL_N, + IQS62X_SW_HALL_S, +}; + +static const char * const iqs62x_switch_names[] = { + [IQS62X_SW_HALL_N] = "hall-switch-north", + [IQS62X_SW_HALL_S] = "hall-switch-south", +}; + +struct iqs62x_switch_desc { + enum iqs62x_event_flag flag; + unsigned int code; + bool enabled; +}; + +struct iqs62x_keys_private { + struct iqs62x_core *iqs62x; + struct input_dev *input; + struct notifier_block notifier; + struct iqs62x_switch_desc switches[ARRAY_SIZE(iqs62x_switch_names)]; + unsigned int keycode[IQS62X_NUM_KEYS]; + unsigned int keycodemax; + u8 interval; +}; + +static int iqs62x_keys_parse_prop(struct platform_device *pdev, + struct iqs62x_keys_private *iqs62x_keys) +{ + struct fwnode_handle *child; + unsigned int val; + int ret, i; + + ret = device_property_count_u32(&pdev->dev, "linux,keycodes"); + if (ret > IQS62X_NUM_KEYS) { + dev_err(&pdev->dev, "Too many keycodes present\n"); + return -EINVAL; + } else if (ret < 0) { + dev_err(&pdev->dev, "Failed to count keycodes: %d\n", ret); + return ret; + } + iqs62x_keys->keycodemax = ret; + + ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes", + iqs62x_keys->keycode, + iqs62x_keys->keycodemax); + if (ret) { + dev_err(&pdev->dev, "Failed to read keycodes: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) { + child = device_get_named_child_node(&pdev->dev, + iqs62x_switch_names[i]); + if (!child) + continue; + + ret = fwnode_property_read_u32(child, "linux,code", &val); + if (ret) { + dev_err(&pdev->dev, "Failed to read switch code: %d\n", + ret); + return ret; + } + iqs62x_keys->switches[i].code = val; + iqs62x_keys->switches[i].enabled = true; + + if (fwnode_property_present(child, "azoteq,use-prox")) + iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ? + IQS62X_EVENT_HALL_N_P : + IQS62X_EVENT_HALL_S_P); + else + iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ? + IQS62X_EVENT_HALL_N_T : + IQS62X_EVENT_HALL_S_T); + } + + return 0; +} + +static int iqs62x_keys_init(struct iqs62x_keys_private *iqs62x_keys) +{ + struct iqs62x_core *iqs62x = iqs62x_keys->iqs62x; + enum iqs62x_event_flag flag; + unsigned int event_reg, val; + unsigned int event_mask = 0; + int ret, i; + + switch (iqs62x->dev_desc->prod_num) { + case IQS620_PROD_NUM: + case IQS621_PROD_NUM: + case IQS622_PROD_NUM: + event_reg = IQS620_GLBL_EVENT_MASK; + + /* + * Discreet button, hysteresis and SAR UI flags represent keys + * and are unmasked if mapped to a valid keycode. + */ + for (i = 0; i < iqs62x_keys->keycodemax; i++) { + if (iqs62x_keys->keycode[i] == KEY_RESERVED) + continue; + + if (iqs62x_events[i].reg == IQS62X_EVENT_PROX) + event_mask |= iqs62x->dev_desc->prox_mask; + else if (iqs62x_events[i].reg == IQS62X_EVENT_HYST) + event_mask |= (iqs62x->dev_desc->hyst_mask | + iqs62x->dev_desc->sar_mask); + } + + ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->hall_flags, + &val); + if (ret) + return ret; + + /* + * Hall UI flags represent switches and are unmasked if their + * corresponding child nodes are present. + */ + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) { + if (!(iqs62x_keys->switches[i].enabled)) + continue; + + flag = iqs62x_keys->switches[i].flag; + + if (iqs62x_events[flag].reg != IQS62X_EVENT_HALL) + continue; + + event_mask |= iqs62x->dev_desc->hall_mask; + + input_report_switch(iqs62x_keys->input, + iqs62x_keys->switches[i].code, + (val & iqs62x_events[flag].mask) == + iqs62x_events[flag].val); + } + + input_sync(iqs62x_keys->input); + break; + + case IQS624_PROD_NUM: + event_reg = IQS624_HALL_UI; + + /* + * Interval change events represent keys and are unmasked if + * either wheel movement flag is mapped to a valid keycode. + */ + if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP] != KEY_RESERVED) + event_mask |= IQS624_HALL_UI_INT_EVENT; + + if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN] != KEY_RESERVED) + event_mask |= IQS624_HALL_UI_INT_EVENT; + + ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval, + &val); + if (ret) + return ret; + + iqs62x_keys->interval = val; + break; + + default: + return 0; + } + + return regmap_update_bits(iqs62x->regmap, event_reg, event_mask, 0); +} + +static int iqs62x_keys_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs62x_keys_private *iqs62x_keys; + int ret, i; + + iqs62x_keys = container_of(notifier, struct iqs62x_keys_private, + notifier); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs62x_keys_init(iqs62x_keys); + if (ret) { + dev_err(iqs62x_keys->input->dev.parent, + "Failed to re-initialize device: %d\n", ret); + return NOTIFY_BAD; + } + + return NOTIFY_OK; + } + + for (i = 0; i < iqs62x_keys->keycodemax; i++) { + if (iqs62x_events[i].reg == IQS62X_EVENT_WHEEL && + event_data->interval == iqs62x_keys->interval) + continue; + + input_report_key(iqs62x_keys->input, iqs62x_keys->keycode[i], + event_flags & BIT(i)); + } + + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) + if (iqs62x_keys->switches[i].enabled) + input_report_switch(iqs62x_keys->input, + iqs62x_keys->switches[i].code, + event_flags & + BIT(iqs62x_keys->switches[i].flag)); + + input_sync(iqs62x_keys->input); + + if (event_data->interval == iqs62x_keys->interval) + return NOTIFY_OK; + + /* + * Each frame contains at most one wheel event (up or down), in which + * case a complementary release cycle is emulated. + */ + if (event_flags & BIT(IQS62X_EVENT_WHEEL_UP)) { + input_report_key(iqs62x_keys->input, + iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP], + 0); + input_sync(iqs62x_keys->input); + } else if (event_flags & BIT(IQS62X_EVENT_WHEEL_DN)) { + input_report_key(iqs62x_keys->input, + iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN], + 0); + input_sync(iqs62x_keys->input); + } + + iqs62x_keys->interval = event_data->interval; + + return NOTIFY_OK; +} + +static int iqs62x_keys_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs62x_keys_private *iqs62x_keys; + struct input_dev *input; + int ret, i; + + iqs62x_keys = devm_kzalloc(&pdev->dev, sizeof(*iqs62x_keys), + GFP_KERNEL); + if (!iqs62x_keys) + return -ENOMEM; + + platform_set_drvdata(pdev, iqs62x_keys); + + ret = iqs62x_keys_parse_prop(pdev, iqs62x_keys); + if (ret) + return ret; + + input = devm_input_allocate_device(&pdev->dev); + if (!input) + return -ENOMEM; + + input->keycodemax = iqs62x_keys->keycodemax; + input->keycode = iqs62x_keys->keycode; + input->keycodesize = sizeof(*iqs62x_keys->keycode); + + input->name = iqs62x->dev_desc->dev_name; + input->id.bustype = BUS_I2C; + + for (i = 0; i < iqs62x_keys->keycodemax; i++) + if (iqs62x_keys->keycode[i] != KEY_RESERVED) + input_set_capability(input, EV_KEY, + iqs62x_keys->keycode[i]); + + for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) + if (iqs62x_keys->switches[i].enabled) + input_set_capability(input, EV_SW, + iqs62x_keys->switches[i].code); + + iqs62x_keys->iqs62x = iqs62x; + iqs62x_keys->input = input; + + ret = iqs62x_keys_init(iqs62x_keys); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize device: %d\n", ret); + return ret; + } + + ret = input_register_device(iqs62x_keys->input); + if (ret) { + dev_err(&pdev->dev, "Failed to register device: %d\n", ret); + return ret; + } + + iqs62x_keys->notifier.notifier_call = iqs62x_keys_notifier; + ret = blocking_notifier_chain_register(&iqs62x_keys->iqs62x->nh, + &iqs62x_keys->notifier); + if (ret) + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + + return ret; +} + +static int iqs62x_keys_remove(struct platform_device *pdev) +{ + struct iqs62x_keys_private *iqs62x_keys = platform_get_drvdata(pdev); + int ret; + + ret = blocking_notifier_chain_unregister(&iqs62x_keys->iqs62x->nh, + &iqs62x_keys->notifier); + if (ret) + dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret); + + return ret; +} + +static struct platform_driver iqs62x_keys_platform_driver = { + .driver = { + .name = "iqs62x-keys", + }, + .probe = iqs62x_keys_probe, + .remove = iqs62x_keys_remove, +}; +module_platform_driver(iqs62x_keys_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Keys and Switches"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs62x-keys"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 2b203290e7b9..0a59249198d3 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -642,6 +642,19 @@ config MFD_IPAQ_MICRO AT90LS8535 microcontroller flashed with a special iPAQ firmware using the custom protocol implemented in this driver. +config MFD_IQS62X + tristate "Azoteq IQS620A/621/622/624/625 core support" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + Say Y here if you want to build core support for the Azoteq IQS620A, + IQS621, IQS622, IQS624 and IQS625 multi-function sensors. Additional + options must be selected to enable device-specific functions. + + To compile this driver as a module, choose M here: the module will + be called iqs62x. + config MFD_JANZ_CMODIO tristate "Janz CMOD-IO PCI MODULbus Carrier Board" select MFD_CORE @@ -893,6 +906,7 @@ config MFD_CPCAP tristate "Support for Motorola CPCAP" depends on SPI depends on OF || COMPILE_TEST + select MFD_CORE select REGMAP_SPI select REGMAP_IRQ help @@ -1058,6 +1072,7 @@ config MFD_RN5T618 depends on OF select MFD_CORE select REGMAP_I2C + select REGMAP_IRQ help Say yes here to add support for the Ricoh RN5T567, RN5T618, RC5T619 PMIC. @@ -1201,7 +1216,7 @@ config AB8500_CORE chip. This connects to U8500 either on the SSP/SPI bus (deprecated since hardware version v1.0) or the I2C bus via PRCMU. It also adds the irq_chip parts for handling the Mixed Signal chip events. - This chip embeds various other multimedia funtionalities as well. + This chip embeds various other multimedia functionalities as well. config AB8500_DEBUG bool "Enable debug info via debugfs" @@ -1851,7 +1866,7 @@ config MFD_WM8994 has on board GPIO and regulator functionality which is supported via the relevant subsystems. This driver provides core support for the WM8994, in order to use the actual - functionaltiy of the device other drivers must be enabled. + functionality of the device other drivers must be enabled. config MFD_WM97xx tristate "Wolfson Microelectronics WM97xx" @@ -1864,7 +1879,7 @@ config MFD_WM97xx designed for smartphone applications. As well as audio functionality it has on board GPIO and a touchscreen functionality which is supported via the relevant subsystems. This driver provides core - support for the WM97xx, in order to use the actual functionaltiy of + support for the WM97xx, in order to use the actual functionality of the device other drivers must be enabled. config MFD_STW481X @@ -1957,7 +1972,7 @@ config MFD_STPMIC1 Support for ST Microelectronics STPMIC1 PMIC. STPMIC1 has power on key, watchdog and regulator functionalities which are supported via the relevant subsystems. This driver provides core support for the - STPMIC1. In order to use the actual functionaltiy of the device other + STPMIC1. In order to use the actual functionality of the device other drivers must be enabled. To compile this driver as a module, choose M here: the diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b83f172545e1..f935d10cbf0f 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -226,6 +226,7 @@ obj-$(CONFIG_MFD_AS3711) += as3711.o obj-$(CONFIG_MFD_AS3722) += as3722.o obj-$(CONFIG_MFD_STW481X) += stw481x.o obj-$(CONFIG_MFD_IPAQ_MICRO) += ipaq-micro.o +obj-$(CONFIG_MFD_IQS62X) += iqs62x.o obj-$(CONFIG_MFD_MENF21BMC) += menf21bmc.o obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-pmic-core.o obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o diff --git a/drivers/mfd/aat2870-core.c b/drivers/mfd/aat2870-core.c index 78ee4b28fca2..a17cf759739d 100644 --- a/drivers/mfd/aat2870-core.c +++ b/drivers/mfd/aat2870-core.c @@ -221,7 +221,7 @@ static ssize_t aat2870_dump_reg(struct aat2870_data *aat2870, char *buf) count += sprintf(buf, "aat2870 registers\n"); for (addr = 0; addr < AAT2870_REG_NUM; addr++) { - count += sprintf(buf + count, "0x%02x: ", addr); + count += snprintf(buf + count, PAGE_SIZE - count, "0x%02x: ", addr); if (count >= PAGE_SIZE - 1) break; diff --git a/drivers/mfd/cros_ec_dev.c b/drivers/mfd/cros_ec_dev.c index 39e611695053..32c2b912b58b 100644 --- a/drivers/mfd/cros_ec_dev.c +++ b/drivers/mfd/cros_ec_dev.c @@ -211,7 +211,7 @@ static int ec_device_probe(struct platform_device *pdev) * explicitly added on platforms that don't have the PD notifier ACPI * device entry defined. */ - if (IS_ENABLED(CONFIG_OF)) { + if (IS_ENABLED(CONFIG_OF) && ec->ec_dev->dev->of_node) { if (cros_ec_check_features(ec, EC_FEATURE_USB_PD)) { retval = mfd_add_hotplug_devices(ec->dev, cros_usbpd_notify_cells, diff --git a/drivers/mfd/da9062-core.c b/drivers/mfd/da9062-core.c index 419c73533401..fc30726e2e27 100644 --- a/drivers/mfd/da9062-core.c +++ b/drivers/mfd/da9062-core.c @@ -21,6 +21,9 @@ #define DA9062_REG_EVENT_B_OFFSET 1 #define DA9062_REG_EVENT_C_OFFSET 2 +#define DA9062_IRQ_LOW 0 +#define DA9062_IRQ_HIGH 1 + static struct regmap_irq da9061_irqs[] = { /* EVENT A */ [DA9061_IRQ_ONKEY] = { @@ -369,6 +372,33 @@ static int da9062_get_device_type(struct da9062 *chip) return ret; } +static u32 da9062_configure_irq_type(struct da9062 *chip, int irq, u32 *trigger) +{ + u32 irq_type = 0; + struct irq_data *irq_data = irq_get_irq_data(irq); + + if (!irq_data) { + dev_err(chip->dev, "Invalid IRQ: %d\n", irq); + return -EINVAL; + } + *trigger = irqd_get_trigger_type(irq_data); + + switch (*trigger) { + case IRQ_TYPE_LEVEL_HIGH: + irq_type = DA9062_IRQ_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + irq_type = DA9062_IRQ_LOW; + break; + default: + dev_warn(chip->dev, "Unsupported IRQ type: %d\n", *trigger); + return -EINVAL; + } + return regmap_update_bits(chip->regmap, DA9062AA_CONFIG_A, + DA9062AA_IRQ_TYPE_MASK, + irq_type << DA9062AA_IRQ_TYPE_SHIFT); +} + static const struct regmap_range da9061_aa_readable_ranges[] = { regmap_reg_range(DA9062AA_PAGE_CON, DA9062AA_STATUS_B), regmap_reg_range(DA9062AA_STATUS_D, DA9062AA_EVENT_C), @@ -388,6 +418,7 @@ static const struct regmap_range da9061_aa_readable_ranges[] = { regmap_reg_range(DA9062AA_VBUCK1_A, DA9062AA_VBUCK4_A), regmap_reg_range(DA9062AA_VBUCK3_A, DA9062AA_VBUCK3_A), regmap_reg_range(DA9062AA_VLDO1_A, DA9062AA_VLDO4_A), + regmap_reg_range(DA9062AA_CONFIG_A, DA9062AA_CONFIG_A), regmap_reg_range(DA9062AA_VBUCK1_B, DA9062AA_VBUCK4_B), regmap_reg_range(DA9062AA_VBUCK3_B, DA9062AA_VBUCK3_B), regmap_reg_range(DA9062AA_VLDO1_B, DA9062AA_VLDO4_B), @@ -417,6 +448,7 @@ static const struct regmap_range da9061_aa_writeable_ranges[] = { regmap_reg_range(DA9062AA_VBUCK1_A, DA9062AA_VBUCK4_A), regmap_reg_range(DA9062AA_VBUCK3_A, DA9062AA_VBUCK3_A), regmap_reg_range(DA9062AA_VLDO1_A, DA9062AA_VLDO4_A), + regmap_reg_range(DA9062AA_CONFIG_A, DA9062AA_CONFIG_A), regmap_reg_range(DA9062AA_VBUCK1_B, DA9062AA_VBUCK4_B), regmap_reg_range(DA9062AA_VBUCK3_B, DA9062AA_VBUCK3_B), regmap_reg_range(DA9062AA_VLDO1_B, DA9062AA_VLDO4_B), @@ -596,6 +628,7 @@ static int da9062_i2c_probe(struct i2c_client *i2c, const struct regmap_irq_chip *irq_chip; const struct regmap_config *config; int cell_num; + u32 trigger_type = 0; int ret; chip = devm_kzalloc(&i2c->dev, sizeof(*chip), GFP_KERNEL); @@ -654,10 +687,15 @@ static int da9062_i2c_probe(struct i2c_client *i2c, if (ret) return ret; + ret = da9062_configure_irq_type(chip, i2c->irq, &trigger_type); + if (ret < 0) { + dev_err(chip->dev, "Failed to configure IRQ type\n"); + return ret; + } + ret = regmap_add_irq_chip(chip->regmap, i2c->irq, - IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED, - -1, irq_chip, - &chip->regmap_irq); + trigger_type | IRQF_SHARED | IRQF_ONESHOT, + -1, irq_chip, &chip->regmap_irq); if (ret) { dev_err(chip->dev, "Failed to request IRQ %d: %d\n", i2c->irq, ret); diff --git a/drivers/mfd/dln2.c b/drivers/mfd/dln2.c index 7841c11411d0..39276fa626d2 100644 --- a/drivers/mfd/dln2.c +++ b/drivers/mfd/dln2.c @@ -90,6 +90,11 @@ struct dln2_mod_rx_slots { spinlock_t lock; }; +enum dln2_endpoint { + DLN2_EP_OUT = 0, + DLN2_EP_IN = 1, +}; + struct dln2_dev { struct usb_device *usb_dev; struct usb_interface *interface; @@ -640,35 +645,56 @@ static int dln2_start_rx_urbs(struct dln2_dev *dln2, gfp_t gfp) return 0; } +enum { + DLN2_ACPI_MATCH_GPIO = 0, + DLN2_ACPI_MATCH_I2C = 1, + DLN2_ACPI_MATCH_SPI = 2, +}; + static struct dln2_platform_data dln2_pdata_gpio = { .handle = DLN2_HANDLE_GPIO, }; +static struct mfd_cell_acpi_match dln2_acpi_match_gpio = { + .adr = DLN2_ACPI_MATCH_GPIO, +}; + /* Only one I2C port seems to be supported on current hardware */ static struct dln2_platform_data dln2_pdata_i2c = { .handle = DLN2_HANDLE_I2C, .port = 0, }; +static struct mfd_cell_acpi_match dln2_acpi_match_i2c = { + .adr = DLN2_ACPI_MATCH_I2C, +}; + /* Only one SPI port supported */ static struct dln2_platform_data dln2_pdata_spi = { .handle = DLN2_HANDLE_SPI, .port = 0, }; +static struct mfd_cell_acpi_match dln2_acpi_match_spi = { + .adr = DLN2_ACPI_MATCH_SPI, +}; + static const struct mfd_cell dln2_devs[] = { { .name = "dln2-gpio", + .acpi_match = &dln2_acpi_match_gpio, .platform_data = &dln2_pdata_gpio, .pdata_size = sizeof(struct dln2_platform_data), }, { .name = "dln2-i2c", + .acpi_match = &dln2_acpi_match_i2c, .platform_data = &dln2_pdata_i2c, .pdata_size = sizeof(struct dln2_platform_data), }, { .name = "dln2-spi", + .acpi_match = &dln2_acpi_match_spi, .platform_data = &dln2_pdata_spi, .pdata_size = sizeof(struct dln2_platform_data), }, @@ -733,10 +759,10 @@ static int dln2_probe(struct usb_interface *interface, hostif->desc.bNumEndpoints < 2) return -ENODEV; - epin = &hostif->endpoint[0].desc; - epout = &hostif->endpoint[1].desc; + epout = &hostif->endpoint[DLN2_EP_OUT].desc; if (!usb_endpoint_is_bulk_out(epout)) return -ENODEV; + epin = &hostif->endpoint[DLN2_EP_IN].desc; if (!usb_endpoint_is_bulk_in(epin)) return -ENODEV; diff --git a/drivers/mfd/intel-lpss-pci.c b/drivers/mfd/intel-lpss-pci.c index c40a6c7d0cf8..7fc0c5d4edff 100644 --- a/drivers/mfd/intel-lpss-pci.c +++ b/drivers/mfd/intel-lpss-pci.c @@ -139,6 +139,11 @@ static const struct intel_lpss_platform_info cnl_i2c_info = { .properties = spt_i2c_properties, }; +static const struct intel_lpss_platform_info ehl_i2c_info = { + .clk_rate = 100000000, + .properties = bxt_i2c_properties, +}; + static const struct pci_device_id intel_lpss_pci_ids[] = { /* CML-LP */ { PCI_VDEVICE(INTEL, 0x02a8), (kernel_ulong_t)&spt_uart_info }, @@ -231,15 +236,15 @@ static const struct pci_device_id intel_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x4b2a), (kernel_ulong_t)&bxt_info }, { PCI_VDEVICE(INTEL, 0x4b2b), (kernel_ulong_t)&bxt_info }, { PCI_VDEVICE(INTEL, 0x4b37), (kernel_ulong_t)&bxt_info }, - { PCI_VDEVICE(INTEL, 0x4b44), (kernel_ulong_t)&bxt_i2c_info }, - { PCI_VDEVICE(INTEL, 0x4b45), (kernel_ulong_t)&bxt_i2c_info }, - { PCI_VDEVICE(INTEL, 0x4b4b), (kernel_ulong_t)&bxt_i2c_info }, - { PCI_VDEVICE(INTEL, 0x4b4c), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b44), (kernel_ulong_t)&ehl_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b45), (kernel_ulong_t)&ehl_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b4b), (kernel_ulong_t)&ehl_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b4c), (kernel_ulong_t)&ehl_i2c_info }, { PCI_VDEVICE(INTEL, 0x4b4d), (kernel_ulong_t)&bxt_uart_info }, - { PCI_VDEVICE(INTEL, 0x4b78), (kernel_ulong_t)&bxt_i2c_info }, - { PCI_VDEVICE(INTEL, 0x4b79), (kernel_ulong_t)&bxt_i2c_info }, - { PCI_VDEVICE(INTEL, 0x4b7a), (kernel_ulong_t)&bxt_i2c_info }, - { PCI_VDEVICE(INTEL, 0x4b7b), (kernel_ulong_t)&bxt_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b78), (kernel_ulong_t)&ehl_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b79), (kernel_ulong_t)&ehl_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b7a), (kernel_ulong_t)&ehl_i2c_info }, + { PCI_VDEVICE(INTEL, 0x4b7b), (kernel_ulong_t)&ehl_i2c_info }, /* JSL */ { PCI_VDEVICE(INTEL, 0x4da8), (kernel_ulong_t)&spt_uart_info }, { PCI_VDEVICE(INTEL, 0x4da9), (kernel_ulong_t)&spt_uart_info }, @@ -347,6 +352,16 @@ static const struct pci_device_id intel_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0xa36a), (kernel_ulong_t)&cnl_i2c_info }, { PCI_VDEVICE(INTEL, 0xa36b), (kernel_ulong_t)&cnl_i2c_info }, { PCI_VDEVICE(INTEL, 0xa37b), (kernel_ulong_t)&spt_info }, + /* CML-V */ + { PCI_VDEVICE(INTEL, 0xa3a7), (kernel_ulong_t)&spt_uart_info }, + { PCI_VDEVICE(INTEL, 0xa3a8), (kernel_ulong_t)&spt_uart_info }, + { PCI_VDEVICE(INTEL, 0xa3a9), (kernel_ulong_t)&spt_info }, + { PCI_VDEVICE(INTEL, 0xa3aa), (kernel_ulong_t)&spt_info }, + { PCI_VDEVICE(INTEL, 0xa3e0), (kernel_ulong_t)&spt_i2c_info }, + { PCI_VDEVICE(INTEL, 0xa3e1), (kernel_ulong_t)&spt_i2c_info }, + { PCI_VDEVICE(INTEL, 0xa3e2), (kernel_ulong_t)&spt_i2c_info }, + { PCI_VDEVICE(INTEL, 0xa3e3), (kernel_ulong_t)&spt_i2c_info }, + { PCI_VDEVICE(INTEL, 0xa3e6), (kernel_ulong_t)&spt_uart_info }, { } }; MODULE_DEVICE_TABLE(pci, intel_lpss_pci_ids); diff --git a/drivers/mfd/iqs62x.c b/drivers/mfd/iqs62x.c new file mode 100644 index 000000000000..af764bc87d7c --- /dev/null +++ b/drivers/mfd/iqs62x.c @@ -0,0 +1,1063 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors + * + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> + * + * These devices rely on application-specific register settings and calibration + * data developed in and exported from a suite of GUIs offered by the vendor. A + * separate tool converts the GUIs' ASCII-based output into a standard firmware + * file parsed by the driver. + * + * Link to datasheets and GUIs: https://www.azoteq.com/ + * + * Link to conversion tool: https://github.com/jlabundy/iqs62x-h2bin.git + */ + +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mfd/core.h> +#include <linux/mfd/iqs62x.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of_device.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#define IQS62X_PROD_NUM 0x00 + +#define IQS62X_SYS_FLAGS 0x10 +#define IQS62X_SYS_FLAGS_IN_ATI BIT(2) + +#define IQS620_HALL_FLAGS 0x16 +#define IQS621_HALL_FLAGS 0x19 +#define IQS622_HALL_FLAGS IQS621_HALL_FLAGS + +#define IQS624_INTERVAL_NUM 0x18 +#define IQS625_INTERVAL_NUM 0x12 + +#define IQS622_PROX_SETTINGS_4 0x48 +#define IQS620_PROX_SETTINGS_4 0x50 +#define IQS620_PROX_SETTINGS_4_SAR_EN BIT(7) + +#define IQS621_ALS_CAL_DIV_LUX 0x82 +#define IQS621_ALS_CAL_DIV_IR 0x83 + +#define IQS620_TEMP_CAL_MULT 0xC2 +#define IQS620_TEMP_CAL_DIV 0xC3 +#define IQS620_TEMP_CAL_OFFS 0xC4 + +#define IQS62X_SYS_SETTINGS 0xD0 +#define IQS62X_SYS_SETTINGS_SOFT_RESET BIT(7) +#define IQS62X_SYS_SETTINGS_ACK_RESET BIT(6) +#define IQS62X_SYS_SETTINGS_EVENT_MODE BIT(5) +#define IQS62X_SYS_SETTINGS_CLK_DIV BIT(4) +#define IQS62X_SYS_SETTINGS_REDO_ATI BIT(1) + +#define IQS62X_PWR_SETTINGS 0xD2 +#define IQS62X_PWR_SETTINGS_DIS_AUTO BIT(5) +#define IQS62X_PWR_SETTINGS_PWR_MODE_MASK (BIT(4) | BIT(3)) +#define IQS62X_PWR_SETTINGS_PWR_MODE_HALT (BIT(4) | BIT(3)) +#define IQS62X_PWR_SETTINGS_PWR_MODE_NORM 0 + +#define IQS62X_OTP_CMD 0xF0 +#define IQS62X_OTP_CMD_FG3 0x13 +#define IQS62X_OTP_DATA 0xF1 +#define IQS62X_MAX_REG 0xFF + +#define IQS62X_HALL_CAL_MASK GENMASK(3, 0) + +#define IQS62X_FW_REC_TYPE_INFO 0 +#define IQS62X_FW_REC_TYPE_PROD 1 +#define IQS62X_FW_REC_TYPE_HALL 2 +#define IQS62X_FW_REC_TYPE_MASK 3 +#define IQS62X_FW_REC_TYPE_DATA 4 + +#define IQS62X_ATI_POLL_SLEEP_US 10000 +#define IQS62X_ATI_POLL_TIMEOUT_US 500000 +#define IQS62X_ATI_STABLE_DELAY_MS 150 + +struct iqs62x_fw_rec { + u8 type; + u8 addr; + u8 len; + u8 data; +} __packed; + +struct iqs62x_fw_blk { + struct list_head list; + u8 addr; + u8 mask; + u8 len; + u8 data[]; +}; + +struct iqs62x_info { + u8 prod_num; + u8 sw_num; + u8 hw_num; +} __packed; + +static int iqs62x_dev_init(struct iqs62x_core *iqs62x) +{ + struct iqs62x_fw_blk *fw_blk; + unsigned int val; + int ret; + u8 clk_div = 1; + + list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) { + if (fw_blk->mask) + ret = regmap_update_bits(iqs62x->regmap, fw_blk->addr, + fw_blk->mask, *fw_blk->data); + else + ret = regmap_raw_write(iqs62x->regmap, fw_blk->addr, + fw_blk->data, fw_blk->len); + if (ret) + return ret; + } + + switch (iqs62x->dev_desc->prod_num) { + case IQS620_PROD_NUM: + case IQS622_PROD_NUM: + ret = regmap_read(iqs62x->regmap, + iqs62x->dev_desc->prox_settings, &val); + if (ret) + return ret; + + if (val & IQS620_PROX_SETTINGS_4_SAR_EN) + iqs62x->ui_sel = IQS62X_UI_SAR1; + + /* fall through */ + + case IQS621_PROD_NUM: + ret = regmap_write(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + IQS620_GLBL_EVENT_MASK_PMU | + iqs62x->dev_desc->prox_mask | + iqs62x->dev_desc->sar_mask | + iqs62x->dev_desc->hall_mask | + iqs62x->dev_desc->hyst_mask | + iqs62x->dev_desc->temp_mask | + iqs62x->dev_desc->als_mask | + iqs62x->dev_desc->ir_mask); + if (ret) + return ret; + break; + + default: + ret = regmap_write(iqs62x->regmap, IQS624_HALL_UI, + IQS624_HALL_UI_WHL_EVENT | + IQS624_HALL_UI_INT_EVENT | + IQS624_HALL_UI_AUTO_CAL); + if (ret) + return ret; + + /* + * The IQS625 default interval divider is below the minimum + * permissible value, and the datasheet mandates that it is + * corrected during initialization (unless an updated value + * has already been provided by firmware). + * + * To protect against an unacceptably low user-entered value + * stored in the firmware, the same check is extended to the + * IQS624 as well. + */ + ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, &val); + if (ret) + return ret; + + if (val >= iqs62x->dev_desc->interval_div) + break; + + ret = regmap_write(iqs62x->regmap, IQS624_INTERVAL_DIV, + iqs62x->dev_desc->interval_div); + if (ret) + return ret; + } + + ret = regmap_read(iqs62x->regmap, IQS62X_SYS_SETTINGS, &val); + if (ret) + return ret; + + if (val & IQS62X_SYS_SETTINGS_CLK_DIV) + clk_div = iqs62x->dev_desc->clk_div; + + ret = regmap_write(iqs62x->regmap, IQS62X_SYS_SETTINGS, val | + IQS62X_SYS_SETTINGS_ACK_RESET | + IQS62X_SYS_SETTINGS_EVENT_MODE | + IQS62X_SYS_SETTINGS_REDO_ATI); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(iqs62x->regmap, IQS62X_SYS_FLAGS, val, + !(val & IQS62X_SYS_FLAGS_IN_ATI), + IQS62X_ATI_POLL_SLEEP_US, + IQS62X_ATI_POLL_TIMEOUT_US * clk_div); + if (ret) + return ret; + + msleep(IQS62X_ATI_STABLE_DELAY_MS * clk_div); + + return 0; +} + +static int iqs62x_firmware_parse(struct iqs62x_core *iqs62x, + const struct firmware *fw) +{ + struct i2c_client *client = iqs62x->client; + struct iqs62x_fw_rec *fw_rec; + struct iqs62x_fw_blk *fw_blk; + unsigned int val; + size_t pos = 0; + int ret = 0; + u8 mask, len, *data; + u8 hall_cal_index = 0; + + while (pos < fw->size) { + if (pos + sizeof(*fw_rec) > fw->size) { + ret = -EINVAL; + break; + } + fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos); + pos += sizeof(*fw_rec); + + if (pos + fw_rec->len - 1 > fw->size) { + ret = -EINVAL; + break; + } + pos += fw_rec->len - 1; + + switch (fw_rec->type) { + case IQS62X_FW_REC_TYPE_INFO: + continue; + + case IQS62X_FW_REC_TYPE_PROD: + if (fw_rec->data == iqs62x->dev_desc->prod_num) + continue; + + dev_err(&client->dev, + "Incompatible product number: 0x%02X\n", + fw_rec->data); + ret = -EINVAL; + break; + + case IQS62X_FW_REC_TYPE_HALL: + if (!hall_cal_index) { + ret = regmap_write(iqs62x->regmap, + IQS62X_OTP_CMD, + IQS62X_OTP_CMD_FG3); + if (ret) + break; + + ret = regmap_read(iqs62x->regmap, + IQS62X_OTP_DATA, &val); + if (ret) + break; + + hall_cal_index = val & IQS62X_HALL_CAL_MASK; + if (!hall_cal_index) { + dev_err(&client->dev, + "Uncalibrated device\n"); + ret = -ENODATA; + break; + } + } + + if (hall_cal_index > fw_rec->len) { + ret = -EINVAL; + break; + } + + mask = 0; + data = &fw_rec->data + hall_cal_index - 1; + len = sizeof(*data); + break; + + case IQS62X_FW_REC_TYPE_MASK: + if (fw_rec->len < (sizeof(mask) + sizeof(*data))) { + ret = -EINVAL; + break; + } + + mask = fw_rec->data; + data = &fw_rec->data + sizeof(mask); + len = sizeof(*data); + break; + + case IQS62X_FW_REC_TYPE_DATA: + mask = 0; + data = &fw_rec->data; + len = fw_rec->len; + break; + + default: + dev_err(&client->dev, + "Unrecognized record type: 0x%02X\n", + fw_rec->type); + ret = -EINVAL; + } + + if (ret) + break; + + fw_blk = devm_kzalloc(&client->dev, + struct_size(fw_blk, data, len), + GFP_KERNEL); + if (!fw_blk) { + ret = -ENOMEM; + break; + } + + fw_blk->addr = fw_rec->addr; + fw_blk->mask = mask; + fw_blk->len = len; + memcpy(fw_blk->data, data, len); + + list_add(&fw_blk->list, &iqs62x->fw_blk_head); + } + + release_firmware(fw); + + return ret; +} + +const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS] = { + [IQS62X_EVENT_PROX_CH0_T] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(4), + .val = BIT(4), + }, + [IQS62X_EVENT_PROX_CH0_P] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(0), + .val = BIT(0), + }, + [IQS62X_EVENT_PROX_CH1_T] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(5), + .val = BIT(5), + }, + [IQS62X_EVENT_PROX_CH1_P] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(1), + .val = BIT(1), + }, + [IQS62X_EVENT_PROX_CH2_T] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(6), + .val = BIT(6), + }, + [IQS62X_EVENT_PROX_CH2_P] = { + .reg = IQS62X_EVENT_PROX, + .mask = BIT(2), + .val = BIT(2), + }, + [IQS62X_EVENT_HYST_POS_T] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(6) | BIT(7), + .val = BIT(6), + }, + [IQS62X_EVENT_HYST_POS_P] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(5) | BIT(7), + .val = BIT(5), + }, + [IQS62X_EVENT_HYST_NEG_T] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(6) | BIT(7), + .val = BIT(6) | BIT(7), + }, + [IQS62X_EVENT_HYST_NEG_P] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(5) | BIT(7), + .val = BIT(5) | BIT(7), + }, + [IQS62X_EVENT_SAR1_ACT] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(4), + .val = BIT(4), + }, + [IQS62X_EVENT_SAR1_QRD] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(2), + .val = BIT(2), + }, + [IQS62X_EVENT_SAR1_MOVE] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(1), + .val = BIT(1), + }, + [IQS62X_EVENT_SAR1_HALT] = { + .reg = IQS62X_EVENT_HYST, + .mask = BIT(0), + .val = BIT(0), + }, + [IQS62X_EVENT_WHEEL_UP] = { + .reg = IQS62X_EVENT_WHEEL, + .mask = BIT(7) | BIT(6), + .val = BIT(7), + }, + [IQS62X_EVENT_WHEEL_DN] = { + .reg = IQS62X_EVENT_WHEEL, + .mask = BIT(7) | BIT(6), + .val = BIT(7) | BIT(6), + }, + [IQS62X_EVENT_HALL_N_T] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(2) | BIT(0), + .val = BIT(2), + }, + [IQS62X_EVENT_HALL_N_P] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(1) | BIT(0), + .val = BIT(1), + }, + [IQS62X_EVENT_HALL_S_T] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(2) | BIT(0), + .val = BIT(2) | BIT(0), + }, + [IQS62X_EVENT_HALL_S_P] = { + .reg = IQS62X_EVENT_HALL, + .mask = BIT(1) | BIT(0), + .val = BIT(1) | BIT(0), + }, + [IQS62X_EVENT_SYS_RESET] = { + .reg = IQS62X_EVENT_SYS, + .mask = BIT(7), + .val = BIT(7), + }, +}; +EXPORT_SYMBOL_GPL(iqs62x_events); + +static irqreturn_t iqs62x_irq(int irq, void *context) +{ + struct iqs62x_core *iqs62x = context; + struct i2c_client *client = iqs62x->client; + struct iqs62x_event_data event_data; + struct iqs62x_event_desc event_desc; + enum iqs62x_event_reg event_reg; + unsigned long event_flags = 0; + int ret, i, j; + u8 event_map[IQS62X_EVENT_SIZE]; + + /* + * The device asserts the RDY output to signal the beginning of a + * communication window, which is closed by an I2C stop condition. + * As such, all interrupt status is captured in a single read and + * broadcast to any interested sub-device drivers. + */ + ret = regmap_raw_read(iqs62x->regmap, IQS62X_SYS_FLAGS, event_map, + sizeof(event_map)); + if (ret) { + dev_err(&client->dev, "Failed to read device status: %d\n", + ret); + return IRQ_NONE; + } + + for (i = 0; i < sizeof(event_map); i++) { + event_reg = iqs62x->dev_desc->event_regs[iqs62x->ui_sel][i]; + + switch (event_reg) { + case IQS62X_EVENT_UI_LO: + event_data.ui_data = get_unaligned_le16(&event_map[i]); + + /* fall through */ + + case IQS62X_EVENT_UI_HI: + case IQS62X_EVENT_NONE: + continue; + + case IQS62X_EVENT_ALS: + event_data.als_flags = event_map[i]; + continue; + + case IQS62X_EVENT_IR: + event_data.ir_flags = event_map[i]; + continue; + + case IQS62X_EVENT_INTER: + event_data.interval = event_map[i]; + continue; + + case IQS62X_EVENT_HYST: + event_map[i] <<= iqs62x->dev_desc->hyst_shift; + + /* fall through */ + + case IQS62X_EVENT_WHEEL: + case IQS62X_EVENT_HALL: + case IQS62X_EVENT_PROX: + case IQS62X_EVENT_SYS: + break; + } + + for (j = 0; j < IQS62X_NUM_EVENTS; j++) { + event_desc = iqs62x_events[j]; + + if (event_desc.reg != event_reg) + continue; + + if ((event_map[i] & event_desc.mask) == event_desc.val) + event_flags |= BIT(j); + } + } + + /* + * The device resets itself in response to the I2C master stalling + * communication past a fixed timeout. In this case, all registers + * are restored and any interested sub-device drivers are notified. + */ + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + dev_err(&client->dev, "Unexpected device reset\n"); + + ret = iqs62x_dev_init(iqs62x); + if (ret) { + dev_err(&client->dev, + "Failed to re-initialize device: %d\n", ret); + return IRQ_NONE; + } + } + + ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags, + &event_data); + if (ret & NOTIFY_STOP_MASK) + return IRQ_NONE; + + /* + * Once the communication window is closed, a small delay is added to + * ensure the device's RDY output has been deasserted by the time the + * interrupt handler returns. + */ + usleep_range(50, 100); + + return IRQ_HANDLED; +} + +static void iqs62x_firmware_load(const struct firmware *fw, void *context) +{ + struct iqs62x_core *iqs62x = context; + struct i2c_client *client = iqs62x->client; + int ret; + + if (fw) { + ret = iqs62x_firmware_parse(iqs62x, fw); + if (ret) { + dev_err(&client->dev, "Failed to parse firmware: %d\n", + ret); + goto err_out; + } + } + + ret = iqs62x_dev_init(iqs62x); + if (ret) { + dev_err(&client->dev, "Failed to initialize device: %d\n", ret); + goto err_out; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, iqs62x_irq, IRQF_ONESHOT, + client->name, iqs62x); + if (ret) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", ret); + goto err_out; + } + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, + iqs62x->dev_desc->sub_devs, + iqs62x->dev_desc->num_sub_devs, + NULL, 0, NULL); + if (ret) + dev_err(&client->dev, "Failed to add sub-devices: %d\n", ret); + +err_out: + complete_all(&iqs62x->fw_done); +} + +static const struct mfd_cell iqs620at_sub_devs[] = { + { + .name = "iqs62x-keys", + .of_compatible = "azoteq,iqs620a-keys", + }, + { + .name = "iqs620a-pwm", + .of_compatible = "azoteq,iqs620a-pwm", + }, + { .name = "iqs620at-temp", }, +}; + +static const struct mfd_cell iqs620a_sub_devs[] = { + { + .name = "iqs62x-keys", + .of_compatible = "azoteq,iqs620a-keys", + }, + { + .name = "iqs620a-pwm", + .of_compatible = "azoteq,iqs620a-pwm", + }, +}; + +static const struct mfd_cell iqs621_sub_devs[] = { + { + .name = "iqs62x-keys", + .of_compatible = "azoteq,iqs621-keys", + }, + { .name = "iqs621-als", }, +}; + +static const struct mfd_cell iqs622_sub_devs[] = { + { + .name = "iqs62x-keys", + .of_compatible = "azoteq,iqs622-keys", + }, + { .name = "iqs621-als", }, +}; + +static const struct mfd_cell iqs624_sub_devs[] = { + { + .name = "iqs62x-keys", + .of_compatible = "azoteq,iqs624-keys", + }, + { .name = "iqs624-pos", }, +}; + +static const struct mfd_cell iqs625_sub_devs[] = { + { + .name = "iqs62x-keys", + .of_compatible = "azoteq,iqs625-keys", + }, + { .name = "iqs624-pos", }, +}; + +static const u8 iqs620at_cal_regs[] = { + IQS620_TEMP_CAL_MULT, + IQS620_TEMP_CAL_DIV, + IQS620_TEMP_CAL_OFFS, +}; + +static const u8 iqs621_cal_regs[] = { + IQS621_ALS_CAL_DIV_LUX, + IQS621_ALS_CAL_DIV_IR, +}; + +static const enum iqs62x_event_reg iqs620a_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HALL, /* 0x16 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, + [IQS62X_UI_SAR1] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HALL, /* 0x16 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, +}; + +static const enum iqs62x_event_reg iqs621_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_ALS, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + }, +}; + +static const enum iqs62x_event_reg iqs622_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_ALS, /* 0x14 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_IR, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + }, + [IQS62X_UI_SAR1] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_HYST, /* 0x13 */ + IQS62X_EVENT_ALS, /* 0x14 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_IR, /* 0x16 */ + IQS62X_EVENT_UI_LO, /* 0x17 */ + IQS62X_EVENT_UI_HI, /* 0x18 */ + IQS62X_EVENT_HALL, /* 0x19 */ + }, +}; + +static const enum iqs62x_event_reg iqs624_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_PROX, /* 0x12 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_WHEEL, /* 0x14 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_UI_LO, /* 0x16 */ + IQS62X_EVENT_UI_HI, /* 0x17 */ + IQS62X_EVENT_INTER, /* 0x18 */ + IQS62X_EVENT_NONE, + }, +}; + +static const enum iqs62x_event_reg iqs625_event_regs[][IQS62X_EVENT_SIZE] = { + [IQS62X_UI_PROX] = { + IQS62X_EVENT_SYS, /* 0x10 */ + IQS62X_EVENT_PROX, /* 0x11 */ + IQS62X_EVENT_INTER, /* 0x12 */ + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + IQS62X_EVENT_NONE, + }, +}; + +static const struct iqs62x_dev_desc iqs62x_devs[] = { + { + .dev_name = "iqs620at", + .sub_devs = iqs620at_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs620at_sub_devs), + + .prod_num = IQS620_PROD_NUM, + .sw_num = 0x08, + .cal_regs = iqs620at_cal_regs, + .num_cal_regs = ARRAY_SIZE(iqs620at_cal_regs), + + .prox_mask = BIT(0), + .sar_mask = BIT(1) | BIT(7), + .hall_mask = BIT(2), + .hyst_mask = BIT(3), + .temp_mask = BIT(4), + + .prox_settings = IQS620_PROX_SETTINGS_4, + .hall_flags = IQS620_HALL_FLAGS, + + .clk_div = 4, + .fw_name = "iqs620a.bin", + .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX], + }, + { + .dev_name = "iqs620a", + .sub_devs = iqs620a_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs620a_sub_devs), + + .prod_num = IQS620_PROD_NUM, + .sw_num = 0x08, + + .prox_mask = BIT(0), + .sar_mask = BIT(1) | BIT(7), + .hall_mask = BIT(2), + .hyst_mask = BIT(3), + .temp_mask = BIT(4), + + .prox_settings = IQS620_PROX_SETTINGS_4, + .hall_flags = IQS620_HALL_FLAGS, + + .clk_div = 4, + .fw_name = "iqs620a.bin", + .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX], + }, + { + .dev_name = "iqs621", + .sub_devs = iqs621_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs621_sub_devs), + + .prod_num = IQS621_PROD_NUM, + .sw_num = 0x09, + .cal_regs = iqs621_cal_regs, + .num_cal_regs = ARRAY_SIZE(iqs621_cal_regs), + + .prox_mask = BIT(0), + .hall_mask = BIT(1), + .als_mask = BIT(2), + .hyst_mask = BIT(3), + .temp_mask = BIT(4), + + .als_flags = IQS621_ALS_FLAGS, + .hall_flags = IQS621_HALL_FLAGS, + .hyst_shift = 5, + + .clk_div = 2, + .fw_name = "iqs621.bin", + .event_regs = &iqs621_event_regs[IQS62X_UI_PROX], + }, + { + .dev_name = "iqs622", + .sub_devs = iqs622_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs622_sub_devs), + + .prod_num = IQS622_PROD_NUM, + .sw_num = 0x06, + + .prox_mask = BIT(0), + .sar_mask = BIT(1), + .hall_mask = BIT(2), + .als_mask = BIT(3), + .ir_mask = BIT(4), + + .prox_settings = IQS622_PROX_SETTINGS_4, + .als_flags = IQS622_ALS_FLAGS, + .hall_flags = IQS622_HALL_FLAGS, + + .clk_div = 2, + .fw_name = "iqs622.bin", + .event_regs = &iqs622_event_regs[IQS62X_UI_PROX], + }, + { + .dev_name = "iqs624", + .sub_devs = iqs624_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs624_sub_devs), + + .prod_num = IQS624_PROD_NUM, + .sw_num = 0x0B, + + .interval = IQS624_INTERVAL_NUM, + .interval_div = 3, + + .clk_div = 2, + .fw_name = "iqs624.bin", + .event_regs = &iqs624_event_regs[IQS62X_UI_PROX], + }, + { + .dev_name = "iqs625", + .sub_devs = iqs625_sub_devs, + .num_sub_devs = ARRAY_SIZE(iqs625_sub_devs), + + .prod_num = IQS625_PROD_NUM, + .sw_num = 0x0B, + + .interval = IQS625_INTERVAL_NUM, + .interval_div = 10, + + .clk_div = 2, + .fw_name = "iqs625.bin", + .event_regs = &iqs625_event_regs[IQS62X_UI_PROX], + }, +}; + +static const struct regmap_config iqs62x_map_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IQS62X_MAX_REG, +}; + +static int iqs62x_probe(struct i2c_client *client) +{ + struct iqs62x_core *iqs62x; + struct iqs62x_info info; + unsigned int val; + int ret, i, j; + u8 sw_num = 0; + const char *fw_name = NULL; + + iqs62x = devm_kzalloc(&client->dev, sizeof(*iqs62x), GFP_KERNEL); + if (!iqs62x) + return -ENOMEM; + + i2c_set_clientdata(client, iqs62x); + iqs62x->client = client; + + BLOCKING_INIT_NOTIFIER_HEAD(&iqs62x->nh); + INIT_LIST_HEAD(&iqs62x->fw_blk_head); + init_completion(&iqs62x->fw_done); + + iqs62x->regmap = devm_regmap_init_i2c(client, &iqs62x_map_config); + if (IS_ERR(iqs62x->regmap)) { + ret = PTR_ERR(iqs62x->regmap); + dev_err(&client->dev, "Failed to initialize register map: %d\n", + ret); + return ret; + } + + ret = regmap_raw_read(iqs62x->regmap, IQS62X_PROD_NUM, &info, + sizeof(info)); + if (ret) + return ret; + + /* + * The following sequence validates the device's product and software + * numbers. It then determines if the device is factory-calibrated by + * checking for nonzero values in the device's designated calibration + * registers (if applicable). Depending on the device, the absence of + * calibration data indicates a reduced feature set or invalid device. + * + * For devices given in both calibrated and uncalibrated versions, the + * calibrated version (e.g. IQS620AT) appears first in the iqs62x_devs + * array. The uncalibrated version (e.g. IQS620A) appears next and has + * the same product and software numbers, but no calibration registers + * are specified. + */ + for (i = 0; i < ARRAY_SIZE(iqs62x_devs); i++) { + if (info.prod_num != iqs62x_devs[i].prod_num) + continue; + + iqs62x->dev_desc = &iqs62x_devs[i]; + + if (info.sw_num < iqs62x->dev_desc->sw_num) + continue; + + sw_num = info.sw_num; + + /* + * Read each of the device's designated calibration registers, + * if any, and exit from the inner loop early if any are equal + * to zero (indicating the device is uncalibrated). This could + * be acceptable depending on the device (e.g. IQS620A instead + * of IQS620AT). + */ + for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) { + ret = regmap_read(iqs62x->regmap, + iqs62x->dev_desc->cal_regs[j], &val); + if (ret) + return ret; + + if (!val) + break; + } + + /* + * If the number of nonzero values read from the device equals + * the number of designated calibration registers (which could + * be zero), exit from the outer loop early to signal that the + * device's product and software numbers match a known device, + * and the device is calibrated (if applicable). + */ + if (j == iqs62x->dev_desc->num_cal_regs) + break; + } + + if (!iqs62x->dev_desc) { + dev_err(&client->dev, "Unrecognized product number: 0x%02X\n", + info.prod_num); + return -EINVAL; + } + + if (!sw_num) { + dev_err(&client->dev, "Unrecognized software number: 0x%02X\n", + info.sw_num); + return -EINVAL; + } + + if (i == ARRAY_SIZE(iqs62x_devs)) { + dev_err(&client->dev, "Uncalibrated device\n"); + return -ENODATA; + } + + device_property_read_string(&client->dev, "firmware-name", &fw_name); + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + fw_name ? : iqs62x->dev_desc->fw_name, + &client->dev, GFP_KERNEL, iqs62x, + iqs62x_firmware_load); + if (ret) + dev_err(&client->dev, "Failed to request firmware: %d\n", ret); + + return ret; +} + +static int iqs62x_remove(struct i2c_client *client) +{ + struct iqs62x_core *iqs62x = i2c_get_clientdata(client); + + wait_for_completion(&iqs62x->fw_done); + + return 0; +} + +static int __maybe_unused iqs62x_suspend(struct device *dev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(dev); + int ret; + + wait_for_completion(&iqs62x->fw_done); + + /* + * As per the datasheet, automatic mode switching must be disabled + * before the device is placed in or taken out of halt mode. + */ + ret = regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_DIS_AUTO, 0xFF); + if (ret) + return ret; + + return regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_PWR_MODE_MASK, + IQS62X_PWR_SETTINGS_PWR_MODE_HALT); +} + +static int __maybe_unused iqs62x_resume(struct device *dev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_PWR_MODE_MASK, + IQS62X_PWR_SETTINGS_PWR_MODE_NORM); + if (ret) + return ret; + + return regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS, + IQS62X_PWR_SETTINGS_DIS_AUTO, 0); +} + +static SIMPLE_DEV_PM_OPS(iqs62x_pm, iqs62x_suspend, iqs62x_resume); + +static const struct of_device_id iqs62x_of_match[] = { + { .compatible = "azoteq,iqs620a" }, + { .compatible = "azoteq,iqs621" }, + { .compatible = "azoteq,iqs622" }, + { .compatible = "azoteq,iqs624" }, + { .compatible = "azoteq,iqs625" }, + { } +}; +MODULE_DEVICE_TABLE(of, iqs62x_of_match); + +static struct i2c_driver iqs62x_i2c_driver = { + .driver = { + .name = "iqs62x", + .of_match_table = iqs62x_of_match, + .pm = &iqs62x_pm, + }, + .probe_new = iqs62x_probe, + .remove = iqs62x_remove, +}; +module_i2c_driver(iqs62x_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>"); +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Multi-Function Sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/omap-usb-host.c b/drivers/mfd/omap-usb-host.c index 4798d9f3f9d5..1f4f01b02d98 100644 --- a/drivers/mfd/omap-usb-host.c +++ b/drivers/mfd/omap-usb-host.c @@ -840,7 +840,7 @@ MODULE_DEVICE_TABLE(of, usbhs_omap_dt_ids); static struct platform_driver usbhs_omap_driver = { .driver = { - .name = (char *)usbhs_driver_name, + .name = usbhs_driver_name, .pm = &usbhsomap_dev_pm_ops, .of_match_table = usbhs_omap_dt_ids, }, diff --git a/drivers/mfd/omap-usb-tll.c b/drivers/mfd/omap-usb-tll.c index 265f5e350e1c..4b7f73c317e8 100644 --- a/drivers/mfd/omap-usb-tll.c +++ b/drivers/mfd/omap-usb-tll.c @@ -99,7 +99,7 @@ struct usbtll_omap { void __iomem *base; int nch; /* num. of channels */ - struct clk *ch_clk[0]; /* must be the last member */ + struct clk *ch_clk[]; /* must be the last member */ }; /*-------------------------------------------------------------------------*/ @@ -304,7 +304,7 @@ MODULE_DEVICE_TABLE(of, usbtll_omap_dt_ids); static struct platform_driver usbtll_omap_driver = { .driver = { - .name = (char *)usbtll_driver_name, + .name = usbtll_driver_name, .of_match_table = usbtll_omap_dt_ids, }, .probe = usbtll_omap_probe, diff --git a/drivers/mfd/qcom-pm8xxx.c b/drivers/mfd/qcom-pm8xxx.c index 29133326c6fd..acd172ddcbd6 100644 --- a/drivers/mfd/qcom-pm8xxx.c +++ b/drivers/mfd/qcom-pm8xxx.c @@ -76,7 +76,7 @@ struct pm_irq_chip { unsigned int num_masters; const struct pm_irq_data *pm_irq_data; /* MUST BE AT THE END OF THIS STRUCT */ - u8 config[0]; + u8 config[]; }; static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, unsigned int bp, diff --git a/drivers/mfd/rk808.c b/drivers/mfd/rk808.c index a69a6742ecdc..d109b9f14407 100644 --- a/drivers/mfd/rk808.c +++ b/drivers/mfd/rk808.c @@ -19,7 +19,6 @@ #include <linux/module.h> #include <linux/of_device.h> #include <linux/regmap.h> -#include <linux/syscore_ops.h> struct rk808_reg_data { int addr; @@ -186,7 +185,6 @@ static const struct rk808_reg_data rk805_pre_init_reg[] = { {RK805_BUCK4_CONFIG_REG, RK805_BUCK3_4_ILMAX_MASK, RK805_BUCK4_ILMAX_3500MA}, {RK805_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_400MA}, - {RK805_GPIO_IO_POL_REG, SLP_SD_MSK, SLEEP_FUN}, {RK805_THERMAL_REG, TEMP_HOTDIE_MSK, TEMP115C}, }; @@ -449,88 +447,60 @@ static const struct regmap_irq_chip rk818_irq_chip = { static struct i2c_client *rk808_i2c_client; -static void rk805_device_shutdown(void) +static void rk808_pm_power_off(void) { int ret; + unsigned int reg, bit; struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); - if (!rk808) - return; - - ret = regmap_update_bits(rk808->regmap, - RK805_DEV_CTRL_REG, - DEV_OFF, DEV_OFF); - if (ret) - dev_err(&rk808_i2c_client->dev, "Failed to shutdown device!\n"); -} - -static void rk805_device_shutdown_prepare(void) -{ - int ret; - struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); - - if (!rk808) - return; - - ret = regmap_update_bits(rk808->regmap, - RK805_GPIO_IO_POL_REG, - SLP_SD_MSK, SHUTDOWN_FUN); - if (ret) - dev_err(&rk808_i2c_client->dev, "Failed to shutdown device!\n"); -} - -static void rk808_device_shutdown(void) -{ - int ret; - struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); - - if (!rk808) - return; - - ret = regmap_update_bits(rk808->regmap, - RK808_DEVCTRL_REG, - DEV_OFF_RST, DEV_OFF_RST); - if (ret) - dev_err(&rk808_i2c_client->dev, "Failed to shutdown device!\n"); -} - -static void rk818_device_shutdown(void) -{ - int ret; - struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); - - if (!rk808) + switch (rk808->variant) { + case RK805_ID: + reg = RK805_DEV_CTRL_REG; + bit = DEV_OFF; + break; + case RK808_ID: + reg = RK808_DEVCTRL_REG, + bit = DEV_OFF_RST; + break; + case RK818_ID: + reg = RK818_DEVCTRL_REG; + bit = DEV_OFF; + break; + default: return; - - ret = regmap_update_bits(rk808->regmap, - RK818_DEVCTRL_REG, - DEV_OFF, DEV_OFF); + } + ret = regmap_update_bits(rk808->regmap, reg, bit, bit); if (ret) dev_err(&rk808_i2c_client->dev, "Failed to shutdown device!\n"); } -static void rk8xx_syscore_shutdown(void) +static void rk8xx_shutdown(struct i2c_client *client) { - struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); + struct rk808 *rk808 = i2c_get_clientdata(client); int ret; - if (system_state == SYSTEM_POWER_OFF && - (rk808->variant == RK809_ID || rk808->variant == RK817_ID)) { + switch (rk808->variant) { + case RK805_ID: + ret = regmap_update_bits(rk808->regmap, + RK805_GPIO_IO_POL_REG, + SLP_SD_MSK, + SHUTDOWN_FUN); + break; + case RK809_ID: + case RK817_ID: ret = regmap_update_bits(rk808->regmap, RK817_SYS_CFG(3), RK817_SLPPIN_FUNC_MSK, SLPPIN_DN_FUN); - if (ret) { - dev_warn(&rk808_i2c_client->dev, - "Cannot switch to power down function\n"); - } + break; + default: + return; } + if (ret) + dev_warn(&client->dev, + "Cannot switch to power down function\n"); } -static struct syscore_ops rk808_syscore_ops = { - .shutdown = rk8xx_syscore_shutdown, -}; - static const struct of_device_id rk808_of_match[] = { { .compatible = "rockchip,rk805" }, { .compatible = "rockchip,rk808" }, @@ -550,7 +520,7 @@ static int rk808_probe(struct i2c_client *client, const struct mfd_cell *cells; int nr_pre_init_regs; int nr_cells; - int pm_off = 0, msb, lsb; + int msb, lsb; unsigned char pmic_id_msb, pmic_id_lsb; int ret; int i; @@ -594,8 +564,6 @@ static int rk808_probe(struct i2c_client *client, nr_pre_init_regs = ARRAY_SIZE(rk805_pre_init_reg); cells = rk805s; nr_cells = ARRAY_SIZE(rk805s); - rk808->pm_pwroff_fn = rk805_device_shutdown; - rk808->pm_pwroff_prep_fn = rk805_device_shutdown_prepare; break; case RK808_ID: rk808->regmap_cfg = &rk808_regmap_config; @@ -604,7 +572,6 @@ static int rk808_probe(struct i2c_client *client, nr_pre_init_regs = ARRAY_SIZE(rk808_pre_init_reg); cells = rk808s; nr_cells = ARRAY_SIZE(rk808s); - rk808->pm_pwroff_fn = rk808_device_shutdown; break; case RK818_ID: rk808->regmap_cfg = &rk818_regmap_config; @@ -613,7 +580,6 @@ static int rk808_probe(struct i2c_client *client, nr_pre_init_regs = ARRAY_SIZE(rk818_pre_init_reg); cells = rk818s; nr_cells = ARRAY_SIZE(rk818s); - rk808->pm_pwroff_fn = rk818_device_shutdown; break; case RK809_ID: case RK817_ID: @@ -623,7 +589,6 @@ static int rk808_probe(struct i2c_client *client, nr_pre_init_regs = ARRAY_SIZE(rk817_pre_init_reg); cells = rk817s; nr_cells = ARRAY_SIZE(rk817s); - register_syscore_ops(&rk808_syscore_ops); break; default: dev_err(&client->dev, "Unsupported RK8XX ID %lu\n", @@ -674,17 +639,9 @@ static int rk808_probe(struct i2c_client *client, goto err_irq; } - pm_off = of_property_read_bool(np, - "rockchip,system-power-controller"); - if (pm_off && !pm_power_off) { + if (of_property_read_bool(np, "rockchip,system-power-controller")) { rk808_i2c_client = client; - pm_power_off = rk808->pm_pwroff_fn; - } - - if (pm_off && !pm_power_off_prepare) { - if (!rk808_i2c_client) - rk808_i2c_client = client; - pm_power_off_prepare = rk808->pm_pwroff_prep_fn; + pm_power_off = rk808_pm_power_off; } return 0; @@ -704,25 +661,24 @@ static int rk808_remove(struct i2c_client *client) * pm_power_off may points to a function from another module. * Check if the pointer is set by us and only then overwrite it. */ - if (rk808->pm_pwroff_fn && pm_power_off == rk808->pm_pwroff_fn) + if (pm_power_off == rk808_pm_power_off) pm_power_off = NULL; - /** - * As above, check if the pointer is set by us before overwrite. - */ - if (rk808->pm_pwroff_prep_fn && - pm_power_off_prepare == rk808->pm_pwroff_prep_fn) - pm_power_off_prepare = NULL; - return 0; } static int __maybe_unused rk8xx_suspend(struct device *dev) { - struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); + struct rk808 *rk808 = i2c_get_clientdata(to_i2c_client(dev)); int ret = 0; switch (rk808->variant) { + case RK805_ID: + ret = regmap_update_bits(rk808->regmap, + RK805_GPIO_IO_POL_REG, + SLP_SD_MSK, + SLEEP_FUN); + break; case RK809_ID: case RK817_ID: ret = regmap_update_bits(rk808->regmap, @@ -739,7 +695,7 @@ static int __maybe_unused rk8xx_suspend(struct device *dev) static int __maybe_unused rk8xx_resume(struct device *dev) { - struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); + struct rk808 *rk808 = i2c_get_clientdata(to_i2c_client(dev)); int ret = 0; switch (rk808->variant) { @@ -766,6 +722,7 @@ static struct i2c_driver rk808_i2c_driver = { }, .probe = rk808_probe, .remove = rk808_remove, + .shutdown = rk8xx_shutdown, }; module_i2c_driver(rk808_i2c_driver); diff --git a/drivers/mfd/rn5t618.c b/drivers/mfd/rn5t618.c index ead2e79036a9..232de50562f9 100644 --- a/drivers/mfd/rn5t618.c +++ b/drivers/mfd/rn5t618.c @@ -8,10 +8,13 @@ #include <linux/delay.h> #include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/mfd/core.h> #include <linux/mfd/rn5t618.h> #include <linux/module.h> #include <linux/of_device.h> +#include <linux/platform_device.h> #include <linux/reboot.h> #include <linux/regmap.h> @@ -20,6 +23,13 @@ static const struct mfd_cell rn5t618_cells[] = { { .name = "rn5t618-wdt" }, }; +static const struct mfd_cell rc5t619_cells[] = { + { .name = "rn5t618-adc" }, + { .name = "rn5t618-regulator" }, + { .name = "rc5t619-rtc" }, + { .name = "rn5t618-wdt" }, +}; + static bool rn5t618_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -32,6 +42,8 @@ static bool rn5t618_volatile_reg(struct device *dev, unsigned int reg) case RN5T618_IR_GPF: case RN5T618_MON_IOIN: case RN5T618_INTMON: + case RN5T618_RTC_CTRL1 ... RN5T618_RTC_CTRL2: + case RN5T618_RTC_SECONDS ... RN5T618_RTC_YEAR: return true; default: return false; @@ -46,9 +58,56 @@ static const struct regmap_config rn5t618_regmap_config = { .cache_type = REGCACHE_RBTREE, }; +static const struct regmap_irq rc5t619_irqs[] = { + REGMAP_IRQ_REG(RN5T618_IRQ_SYS, 0, BIT(0)), + REGMAP_IRQ_REG(RN5T618_IRQ_DCDC, 0, BIT(1)), + REGMAP_IRQ_REG(RN5T618_IRQ_RTC, 0, BIT(2)), + REGMAP_IRQ_REG(RN5T618_IRQ_ADC, 0, BIT(3)), + REGMAP_IRQ_REG(RN5T618_IRQ_GPIO, 0, BIT(4)), + REGMAP_IRQ_REG(RN5T618_IRQ_CHG, 0, BIT(6)), +}; + +static const struct regmap_irq_chip rc5t619_irq_chip = { + .name = "rc5t619", + .irqs = rc5t619_irqs, + .num_irqs = ARRAY_SIZE(rc5t619_irqs), + .num_regs = 1, + .status_base = RN5T618_INTMON, + .mask_base = RN5T618_INTEN, + .mask_invert = true, +}; + static struct rn5t618 *rn5t618_pm_power_off; static struct notifier_block rn5t618_restart_handler; +static int rn5t618_irq_init(struct rn5t618 *rn5t618) +{ + const struct regmap_irq_chip *irq_chip = NULL; + int ret; + + if (!rn5t618->irq) + return 0; + + switch (rn5t618->variant) { + case RC5T619: + irq_chip = &rc5t619_irq_chip; + break; + default: + dev_err(rn5t618->dev, "Currently no IRQ support for variant %d\n", + (int)rn5t618->variant); + return -ENOENT; + } + + ret = devm_regmap_add_irq_chip(rn5t618->dev, rn5t618->regmap, + rn5t618->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + 0, irq_chip, &rn5t618->irq_data); + if (ret) + dev_err(rn5t618->dev, "Failed to register IRQ chip\n"); + + return ret; +} + static void rn5t618_trigger_poweroff_sequence(bool repower) { /* disable automatic repower-on */ @@ -87,8 +146,7 @@ static const struct of_device_id rn5t618_of_match[] = { }; MODULE_DEVICE_TABLE(of, rn5t618_of_match); -static int rn5t618_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static int rn5t618_i2c_probe(struct i2c_client *i2c) { const struct of_device_id *of_id; struct rn5t618 *priv; @@ -106,6 +164,8 @@ static int rn5t618_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, priv); priv->variant = (long)of_id->data; + priv->irq = i2c->irq; + priv->dev = &i2c->dev; priv->regmap = devm_regmap_init_i2c(i2c, &rn5t618_regmap_config); if (IS_ERR(priv->regmap)) { @@ -114,8 +174,16 @@ static int rn5t618_i2c_probe(struct i2c_client *i2c, return ret; } - ret = devm_mfd_add_devices(&i2c->dev, -1, rn5t618_cells, - ARRAY_SIZE(rn5t618_cells), NULL, 0, NULL); + if (priv->variant == RC5T619) + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_NONE, + rc5t619_cells, + ARRAY_SIZE(rc5t619_cells), + NULL, 0, NULL); + else + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_NONE, + rn5t618_cells, + ARRAY_SIZE(rn5t618_cells), + NULL, 0, NULL); if (ret) { dev_err(&i2c->dev, "failed to add sub-devices: %d\n", ret); return ret; @@ -138,7 +206,7 @@ static int rn5t618_i2c_probe(struct i2c_client *i2c, return ret; } - return 0; + return rn5t618_irq_init(priv); } static int rn5t618_i2c_remove(struct i2c_client *i2c) @@ -155,19 +223,38 @@ static int rn5t618_i2c_remove(struct i2c_client *i2c) return 0; } -static const struct i2c_device_id rn5t618_i2c_id[] = { - { } -}; -MODULE_DEVICE_TABLE(i2c, rn5t618_i2c_id); +static int __maybe_unused rn5t618_i2c_suspend(struct device *dev) +{ + struct rn5t618 *priv = dev_get_drvdata(dev); + + if (priv->irq) + disable_irq(priv->irq); + + return 0; +} + +static int __maybe_unused rn5t618_i2c_resume(struct device *dev) +{ + struct rn5t618 *priv = dev_get_drvdata(dev); + + if (priv->irq) + enable_irq(priv->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(rn5t618_i2c_dev_pm_ops, + rn5t618_i2c_suspend, + rn5t618_i2c_resume); static struct i2c_driver rn5t618_i2c_driver = { .driver = { .name = "rn5t618", .of_match_table = of_match_ptr(rn5t618_of_match), + .pm = &rn5t618_i2c_dev_pm_ops, }, - .probe = rn5t618_i2c_probe, + .probe_new = rn5t618_i2c_probe, .remove = rn5t618_i2c_remove, - .id_table = rn5t618_i2c_id, }; module_i2c_driver(rn5t618_i2c_driver); diff --git a/drivers/mfd/sprd-sc27xx-spi.c b/drivers/mfd/sprd-sc27xx-spi.c index c0529a1cd5ea..ebdf2f11ae28 100644 --- a/drivers/mfd/sprd-sc27xx-spi.c +++ b/drivers/mfd/sprd-sc27xx-spi.c @@ -10,6 +10,7 @@ #include <linux/of_device.h> #include <linux/regmap.h> #include <linux/spi/spi.h> +#include <uapi/linux/usb/charger.h> #define SPRD_PMIC_INT_MASK_STATUS 0x0 #define SPRD_PMIC_INT_RAW_STATUS 0x4 @@ -17,6 +18,16 @@ #define SPRD_SC2731_IRQ_BASE 0x140 #define SPRD_SC2731_IRQ_NUMS 16 +#define SPRD_SC2731_CHG_DET 0xedc + +/* PMIC charger detection definition */ +#define SPRD_PMIC_CHG_DET_DELAY_US 200000 +#define SPRD_PMIC_CHG_DET_TIMEOUT 2000000 +#define SPRD_PMIC_CHG_DET_DONE BIT(11) +#define SPRD_PMIC_SDP_TYPE BIT(7) +#define SPRD_PMIC_DCP_TYPE BIT(6) +#define SPRD_PMIC_CDP_TYPE BIT(5) +#define SPRD_PMIC_CHG_TYPE_MASK GENMASK(7, 5) struct sprd_pmic { struct regmap *regmap; @@ -24,12 +35,14 @@ struct sprd_pmic { struct regmap_irq *irqs; struct regmap_irq_chip irq_chip; struct regmap_irq_chip_data *irq_data; + const struct sprd_pmic_data *pdata; int irq; }; struct sprd_pmic_data { u32 irq_base; u32 num_irqs; + u32 charger_det; }; /* @@ -40,8 +53,46 @@ struct sprd_pmic_data { static const struct sprd_pmic_data sc2731_data = { .irq_base = SPRD_SC2731_IRQ_BASE, .num_irqs = SPRD_SC2731_IRQ_NUMS, + .charger_det = SPRD_SC2731_CHG_DET, }; +enum usb_charger_type sprd_pmic_detect_charger_type(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct sprd_pmic *ddata = spi_get_drvdata(spi); + const struct sprd_pmic_data *pdata = ddata->pdata; + enum usb_charger_type type; + u32 val; + int ret; + + ret = regmap_read_poll_timeout(ddata->regmap, pdata->charger_det, val, + (val & SPRD_PMIC_CHG_DET_DONE), + SPRD_PMIC_CHG_DET_DELAY_US, + SPRD_PMIC_CHG_DET_TIMEOUT); + if (ret) { + dev_err(&spi->dev, "failed to detect charger type\n"); + return UNKNOWN_TYPE; + } + + switch (val & SPRD_PMIC_CHG_TYPE_MASK) { + case SPRD_PMIC_CDP_TYPE: + type = CDP_TYPE; + break; + case SPRD_PMIC_DCP_TYPE: + type = DCP_TYPE; + break; + case SPRD_PMIC_SDP_TYPE: + type = SDP_TYPE; + break; + default: + type = UNKNOWN_TYPE; + break; + } + + return type; +} +EXPORT_SYMBOL_GPL(sprd_pmic_detect_charger_type); + static const struct mfd_cell sprd_pmic_devs[] = { { .name = "sc27xx-wdt", @@ -181,6 +232,7 @@ static int sprd_pmic_probe(struct spi_device *spi) spi_set_drvdata(spi, ddata); ddata->dev = &spi->dev; ddata->irq = spi->irq; + ddata->pdata = pdata; ddata->irq_chip.name = dev_name(&spi->dev); ddata->irq_chip.status_base = diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index f942a3302cdc..ec873f09c763 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -590,6 +590,16 @@ config RTC_DRV_RC5T583 This driver can also be built as a module. If so, the module will be called rtc-rc5t583. +config RTC_DRV_RC5T619 + tristate "RICOH RC5T619 RTC driver" + depends on MFD_RN5T618 + help + If you say yes here you get support for the RTC on the + RICOH RC5T619 chips. + + This driver can also be built as a module. If so, the module + will be called rtc-rc5t619. + config RTC_DRV_S35390A tristate "Seiko Instruments S-35390A" select BITREVERSE diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 3b66ee99063f..0721752c6ed4 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -133,6 +133,7 @@ obj-$(CONFIG_RTC_DRV_PXA) += rtc-pxa.o obj-$(CONFIG_RTC_DRV_R7301) += rtc-r7301.o obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o +obj-$(CONFIG_RTC_DRV_RC5T619) += rtc-rc5t619.o obj-$(CONFIG_RTC_DRV_RK808) += rtc-rk808.o obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o diff --git a/drivers/rtc/rtc-rc5t619.c b/drivers/rtc/rtc-rc5t619.c new file mode 100644 index 000000000000..24e386ecbc7e --- /dev/null +++ b/drivers/rtc/rtc-rc5t619.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * drivers/rtc/rtc-rc5t619.c + * + * Real time clock driver for RICOH RC5T619 power management chip. + * + * Copyright (C) 2019 Andreas Kemnade + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mfd/rn5t618.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/bcd.h> +#include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/irqdomain.h> + +struct rc5t619_rtc { + int irq; + struct rtc_device *rtc; + struct rn5t618 *rn5t618; +}; + +#define CTRL1_ALARM_ENABLED 0x40 +#define CTRL1_24HR 0x20 +#define CTRL1_PERIODIC_MASK 0xf + +#define CTRL2_PON 0x10 +#define CTRL2_ALARM_STATUS 0x80 +#define CTRL2_CTFG 0x4 +#define CTRL2_CTC 0x1 + +#define MONTH_CENTFLAG 0x80 +#define HOUR_PMFLAG 0x20 +#define MDAY_DAL_EXT 0x80 + +static uint8_t rtc5t619_12hour_bcd2bin(uint8_t hour) +{ + if (hour & HOUR_PMFLAG) { + hour = bcd2bin(hour & ~HOUR_PMFLAG); + return hour == 12 ? 12 : 12 + hour; + } + + hour = bcd2bin(hour); + return hour == 12 ? 0 : hour; +} + +static uint8_t rtc5t619_12hour_bin2bcd(uint8_t hour) +{ + if (!hour) + return 0x12; + + if (hour < 12) + return bin2bcd(hour); + + if (hour == 12) + return 0x12 | HOUR_PMFLAG; + + return bin2bcd(hour - 12) | HOUR_PMFLAG; +} + +static int rc5t619_rtc_periodic_disable(struct device *dev) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + int err; + + /* disable function */ + err = regmap_update_bits(rtc->rn5t618->regmap, + RN5T618_RTC_CTRL1, CTRL1_PERIODIC_MASK, 0); + if (err < 0) + return err; + + /* clear alarm flag and CTFG */ + err = regmap_update_bits(rtc->rn5t618->regmap, RN5T618_RTC_CTRL2, + CTRL2_ALARM_STATUS | CTRL2_CTFG | CTRL2_CTC, + 0); + if (err < 0) + return err; + + return 0; +} + +/* things to be done once after power on */ +static int rc5t619_rtc_pon_setup(struct device *dev) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + int err; + unsigned int reg_data; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL2, ®_data); + if (err < 0) + return err; + + /* clear VDET PON */ + reg_data &= ~(CTRL2_PON | CTRL2_CTC | 0x4a); /* 0101-1011 */ + reg_data |= 0x20; /* 0010-0000 */ + err = regmap_write(rtc->rn5t618->regmap, RN5T618_RTC_CTRL2, reg_data); + if (err < 0) + return err; + + /* clearing RTC Adjust register */ + err = regmap_write(rtc->rn5t618->regmap, RN5T618_RTC_ADJUST, 0); + if (err) + return err; + + return regmap_update_bits(rtc->rn5t618->regmap, + RN5T618_RTC_CTRL1, + CTRL1_24HR, CTRL1_24HR); +} + +static int rc5t619_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + u8 buff[7]; + int err; + int cent_flag; + unsigned int ctrl1; + unsigned int ctrl2; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL2, &ctrl2); + if (err < 0) + return err; + + if (ctrl2 & CTRL2_PON) + return -EINVAL; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL1, &ctrl1); + if (err < 0) + return err; + + err = regmap_bulk_read(rtc->rn5t618->regmap, RN5T618_RTC_SECONDS, + buff, sizeof(buff)); + if (err < 0) + return err; + + if (buff[5] & MONTH_CENTFLAG) + cent_flag = 1; + else + cent_flag = 0; + + tm->tm_sec = bcd2bin(buff[0]); + tm->tm_min = bcd2bin(buff[1]); + + if (ctrl1 & CTRL1_24HR) + tm->tm_hour = bcd2bin(buff[2]); + else + tm->tm_hour = rtc5t619_12hour_bcd2bin(buff[2]); + + tm->tm_wday = bcd2bin(buff[3]); + tm->tm_mday = bcd2bin(buff[4]); + tm->tm_mon = bcd2bin(buff[5] & 0x1f) - 1; /* back to system 0-11 */ + tm->tm_year = bcd2bin(buff[6]) + 100 * cent_flag; + + return 0; +} + +static int rc5t619_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + u8 buff[7]; + int err; + int cent_flag; + unsigned int ctrl1; + unsigned int ctrl2; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL2, &ctrl2); + if (err < 0) + return err; + + if (ctrl2 & CTRL2_PON) + rc5t619_rtc_pon_setup(dev); + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL1, &ctrl1); + if (err < 0) + return err; + + if (tm->tm_year >= 100) + cent_flag = 1; + else + cent_flag = 0; + + buff[0] = bin2bcd(tm->tm_sec); + buff[1] = bin2bcd(tm->tm_min); + + if (ctrl1 & CTRL1_24HR) + buff[2] = bin2bcd(tm->tm_hour); + else + buff[2] = rtc5t619_12hour_bin2bcd(tm->tm_hour); + + buff[3] = bin2bcd(tm->tm_wday); + buff[4] = bin2bcd(tm->tm_mday); + buff[5] = bin2bcd(tm->tm_mon + 1); /* system set 0-11 */ + buff[6] = bin2bcd(tm->tm_year - cent_flag * 100); + + if (cent_flag) + buff[5] |= MONTH_CENTFLAG; + + err = regmap_bulk_write(rtc->rn5t618->regmap, RN5T618_RTC_SECONDS, + buff, sizeof(buff)); + if (err < 0) { + dev_err(dev, "failed to program new time: %d\n", err); + return err; + } + + return 0; +} + +/* 0-disable, 1-enable */ +static int rc5t619_rtc_alarm_enable(struct device *dev, unsigned int enabled) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + + return regmap_update_bits(rtc->rn5t618->regmap, + RN5T618_RTC_CTRL1, + CTRL1_ALARM_ENABLED, + enabled ? CTRL1_ALARM_ENABLED : 0); +} + +static int rc5t619_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + u8 buff[6]; + unsigned int buff_cent; + int err; + int cent_flag; + unsigned int ctrl1; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL1, &ctrl1); + if (err) + return err; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_MONTH, &buff_cent); + if (err < 0) { + dev_err(dev, "failed to read time: %d\n", err); + return err; + } + + if (buff_cent & MONTH_CENTFLAG) + cent_flag = 1; + else + cent_flag = 0; + + err = regmap_bulk_read(rtc->rn5t618->regmap, RN5T618_RTC_ALARM_Y_SEC, + buff, sizeof(buff)); + if (err) + return err; + + buff[3] = buff[3] & 0x3f; + + alrm->time.tm_sec = bcd2bin(buff[0]); + alrm->time.tm_min = bcd2bin(buff[1]); + + if (ctrl1 & CTRL1_24HR) + alrm->time.tm_hour = bcd2bin(buff[2]); + else + alrm->time.tm_hour = rtc5t619_12hour_bcd2bin(buff[2]); + + alrm->time.tm_mday = bcd2bin(buff[3]); + alrm->time.tm_mon = bcd2bin(buff[4]) - 1; + alrm->time.tm_year = bcd2bin(buff[5]) + 100 * cent_flag; + alrm->enabled = !!(ctrl1 & CTRL1_ALARM_ENABLED); + dev_dbg(dev, "read alarm: %ptR\n", &alrm->time); + + return 0; +} + +static int rc5t619_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + u8 buff[6]; + int err; + int cent_flag; + unsigned int ctrl1; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL1, &ctrl1); + if (err) + return err; + + err = rc5t619_rtc_alarm_enable(dev, 0); + if (err < 0) + return err; + + if (rtc->irq == -1) + return -EINVAL; + + if (alrm->enabled == 0) + return 0; + + if (alrm->time.tm_year >= 100) + cent_flag = 1; + else + cent_flag = 0; + + alrm->time.tm_mon += 1; + buff[0] = bin2bcd(alrm->time.tm_sec); + buff[1] = bin2bcd(alrm->time.tm_min); + + if (ctrl1 & CTRL1_24HR) + buff[2] = bin2bcd(alrm->time.tm_hour); + else + buff[2] = rtc5t619_12hour_bin2bcd(alrm->time.tm_hour); + + buff[3] = bin2bcd(alrm->time.tm_mday); + buff[4] = bin2bcd(alrm->time.tm_mon); + buff[5] = bin2bcd(alrm->time.tm_year - 100 * cent_flag); + buff[3] |= MDAY_DAL_EXT; + + err = regmap_bulk_write(rtc->rn5t618->regmap, RN5T618_RTC_ALARM_Y_SEC, + buff, sizeof(buff)); + if (err < 0) + return err; + + return rc5t619_rtc_alarm_enable(dev, alrm->enabled); +} + +static const struct rtc_class_ops rc5t619_rtc_ops = { + .read_time = rc5t619_rtc_read_time, + .set_time = rc5t619_rtc_set_time, + .set_alarm = rc5t619_rtc_set_alarm, + .read_alarm = rc5t619_rtc_read_alarm, + .alarm_irq_enable = rc5t619_rtc_alarm_enable, +}; + +static int rc5t619_rtc_alarm_flag_clr(struct device *dev) +{ + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + + /* clear alarm-D status bits.*/ + return regmap_update_bits(rtc->rn5t618->regmap, + RN5T618_RTC_CTRL2, + CTRL2_ALARM_STATUS | CTRL2_CTC, 0); +} + +static irqreturn_t rc5t619_rtc_irq(int irq, void *data) +{ + struct device *dev = data; + struct rc5t619_rtc *rtc = dev_get_drvdata(dev); + + rc5t619_rtc_alarm_flag_clr(dev); + + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + return IRQ_HANDLED; +} + +static int rc5t619_rtc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent); + struct rc5t619_rtc *rtc; + unsigned int ctrl2; + int err; + + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); + if (IS_ERR(rtc)) { + err = PTR_ERR(rtc); + return -ENOMEM; + } + + rtc->rn5t618 = rn5t618; + + dev_set_drvdata(dev, rtc); + rtc->irq = -1; + + if (rn5t618->irq_data) + rtc->irq = regmap_irq_get_virq(rn5t618->irq_data, + RN5T618_IRQ_RTC); + + if (rtc->irq < 0) + rtc->irq = -1; + + err = regmap_read(rtc->rn5t618->regmap, RN5T618_RTC_CTRL2, &ctrl2); + if (err < 0) + return err; + + /* disable rtc periodic function */ + err = rc5t619_rtc_periodic_disable(&pdev->dev); + if (err) + return err; + + if (ctrl2 & CTRL2_PON) { + err = rc5t619_rtc_alarm_flag_clr(&pdev->dev); + if (err) + return err; + } + + rtc->rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(rtc->rtc)) { + err = PTR_ERR(rtc->rtc); + dev_err(dev, "RTC device register: err %d\n", err); + return err; + } + + rtc->rtc->ops = &rc5t619_rtc_ops; + rtc->rtc->range_min = RTC_TIMESTAMP_BEGIN_1900; + rtc->rtc->range_max = RTC_TIMESTAMP_END_2099; + + /* set interrupt and enable it */ + if (rtc->irq != -1) { + err = devm_request_threaded_irq(&pdev->dev, rtc->irq, NULL, + rc5t619_rtc_irq, + IRQF_ONESHOT, + "rtc-rc5t619", + &pdev->dev); + if (err < 0) { + dev_err(&pdev->dev, "request IRQ:%d fail\n", rtc->irq); + rtc->irq = -1; + + err = rc5t619_rtc_alarm_enable(&pdev->dev, 0); + if (err) + return err; + + } else { + /* enable wake */ + device_init_wakeup(&pdev->dev, 1); + enable_irq_wake(rtc->irq); + } + } else { + /* system don't want to using alarm interrupt, so close it */ + err = rc5t619_rtc_alarm_enable(&pdev->dev, 0); + if (err) + return err; + + dev_warn(&pdev->dev, "rc5t619 interrupt is disabled\n"); + } + + return rtc_register_device(rtc->rtc); +} + +static struct platform_driver rc5t619_rtc_driver = { + .driver = { + .name = "rc5t619-rtc", + }, + .probe = rc5t619_rtc_probe, +}; + +module_platform_driver(rc5t619_rtc_driver); +MODULE_ALIAS("platform:rc5t619-rtc"); +MODULE_DESCRIPTION("RICOH RC5T619 RTC driver"); +MODULE_LICENSE("GPL"); |