diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 159bac6c5046..9f1fd2290c9b 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -65,3 +65,21 @@ config REGMAP_I3C config REGMAP_SPI_AVMM tristate depends on SPI + +config REGMAP_QTI_DEBUGFS + tristate "Regmap QTI debug feature support" + depends on REGMAP && DEBUG_FS + help + This library provides a runtime debugfs interface to read and write a + subset of regmap registers. This interface is more performant and + easier to use than the traditional method which dumps all registers + defined in a given regmap. + +config REGMAP_QTI_DEBUGFS_ALLOW_WRITE + bool "Allow QTI regmap debugfs write" + depends on REGMAP_QTI_DEBUGFS + help + Say 'y' here to allow regmap debugfs writes within the QTI debugfs + regmap library. Regmap debugfs write could be risky when accessing + essential hardware components, so it is not recommended to enable this + option on production devices. diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 11facb32a027..a083ca7e6662 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_REGMAP_SCCB) += regmap-sccb.o obj-$(CONFIG_REGMAP_I3C) += regmap-i3c.o obj-$(CONFIG_REGMAP_SPI_AVMM) += regmap-spi-avmm.o obj-$(CONFIG_REGMAP_MDIO) += regmap-mdio.o +obj-$(CONFIG_REGMAP_QTI_DEBUGFS) += qti-regmap-debugfs.o diff --git a/drivers/base/regmap/qti-regmap-debugfs.c b/drivers/base/regmap/qti-regmap-debugfs.c new file mode 100644 index 000000000000..371ab815772b --- /dev/null +++ b/drivers/base/regmap/qti-regmap-debugfs.c @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2011 Wolfson Microelectronics plc + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" + +struct regmap_qti_debugfs { + struct list_head list; + struct regmap *regmap; + struct device *dev; + unsigned int dump_address; + unsigned int dump_count; +}; + +static DEFINE_MUTEX(regmap_qti_debugfs_lock); +static LIST_HEAD(regmap_qti_debugfs_list); + +static size_t regmap_calc_reg_len(int max_val) +{ + return snprintf(NULL, 0, "%x", max_val); +} + +static inline void regmap_calc_tot_len(struct regmap *map, + void *buf, size_t count) +{ + /* Calculate the length of a fixed format */ + if (!map->debugfs_tot_len) { + map->debugfs_reg_len = regmap_calc_reg_len(map->max_register), + map->debugfs_val_len = 2 * map->format.val_bytes; + map->debugfs_tot_len = map->debugfs_reg_len + + map->debugfs_val_len + 3; /* : \n */ + } +} + +static bool _regmap_volatile(struct regmap *map, unsigned int reg); + +static int _regcache_read(struct regmap *map, + unsigned int reg, unsigned int *value) +{ + if (map->cache_type == REGCACHE_NONE) + return -ENODEV; + + if (WARN_ON(!map->cache_ops)) + return -EINVAL; + + if (!_regmap_volatile(map, reg)) + return map->cache_ops->read(map, reg, value); + + return -EINVAL; +} + +static bool _regmap_cached(struct regmap *map, unsigned int reg) +{ + int ret; + unsigned int val; + + if (map->cache_type == REGCACHE_NONE) + return false; + + if (!map->cache_ops) + return false; + + if (map->max_register && reg > map->max_register) + return false; + + map->lock(map->lock_arg); + ret = _regcache_read(map, reg, &val); + map->unlock(map->lock_arg); + if (ret) + return false; + + return true; +} + +static bool _regmap_readable(struct regmap *map, unsigned int reg) +{ + if (!map->reg_read) + return false; + + if (map->max_register && reg > map->max_register) + return false; + + if (map->format.format_write) + return false; + + if (map->readable_reg) + return map->readable_reg(map->dev, reg); + + if (map->rd_table) + return regmap_check_range_table(map, reg, map->rd_table); + + return true; +} + +static bool _regmap_volatile(struct regmap *map, unsigned int reg) +{ + if (!map->format.format_write && !_regmap_readable(map, reg)) + return false; + + if (map->volatile_reg) + return map->volatile_reg(map->dev, reg); + + if (map->volatile_table) + return regmap_check_range_table(map, reg, map->volatile_table); + + if (map->cache_ops) + return false; + else + return true; +} + +static bool _regmap_precious(struct regmap *map, unsigned int reg) +{ + if (!_regmap_readable(map, reg)) + return false; + + if (map->precious_reg) + return map->precious_reg(map->dev, reg); + + if (map->precious_table) + return regmap_check_range_table(map, reg, map->precious_table); + + return false; +} + +static bool regmap_printable(struct regmap *map, unsigned int reg) +{ + if (_regmap_precious(map, reg)) + return false; + + if (!_regmap_readable(map, reg) && !_regmap_cached(map, reg)) + return false; + + return true; +} + +static void regmap_debugfs_free_dump_cache(struct regmap *map) +{ + struct regmap_debugfs_off_cache *c; + + while (!list_empty(&map->debugfs_off_cache)) { + c = list_first_entry(&map->debugfs_off_cache, + struct regmap_debugfs_off_cache, + list); + list_del(&c->list); + kfree(c); + } +} + +static int regmap_next_readable_reg(struct regmap *map, int reg) +{ + struct regmap_debugfs_off_cache *c; + int ret = -EINVAL; + + if (regmap_printable(map, reg + map->reg_stride)) { + ret = reg + map->reg_stride; + } else { + mutex_lock(&map->cache_lock); + list_for_each_entry(c, &map->debugfs_off_cache, list) { + if (reg > c->max_reg) + continue; + if (reg < c->base_reg) { + ret = c->base_reg; + break; + } + } + mutex_unlock(&map->cache_lock); + } + return ret; +} + +static int regmap_debugfs_generate_cache(struct regmap *map) +{ + struct regmap_debugfs_off_cache *c = NULL; + loff_t p = 0; + unsigned int i = 0; + + mutex_lock(&map->cache_lock); + + if (list_empty(&map->debugfs_off_cache)) { + for (i = 0; i <= map->max_register; i += map->reg_stride) { + /* Skip unprinted registers, closing off cache entry */ + if (!regmap_printable(map, i)) { + if (c) { + c->max = p - 1; + c->max_reg = i - map->reg_stride; + list_add_tail(&c->list, + &map->debugfs_off_cache); + c = NULL; + } + + continue; + } + + /* No cache entry? Start a new one */ + if (!c) { + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + regmap_debugfs_free_dump_cache(map); + mutex_unlock(&map->cache_lock); + return -ENOMEM; + } + c->min = p; + c->base_reg = i; + } + + p += map->debugfs_tot_len; + } + } + + /* Close the last entry off if we didn't scan beyond it */ + if (c) { + c->max = p - 1; + c->max_reg = i - map->reg_stride; + list_add_tail(&c->list, + &map->debugfs_off_cache); + } + + mutex_unlock(&map->cache_lock); + + return 0; +} + +/* + * Work out where the start offset maps into register numbers, bearing + * in mind that we suppress hidden registers. + */ +static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, + unsigned int base, + loff_t from, + loff_t *pos) +{ + struct regmap_debugfs_off_cache *c = NULL; + unsigned int ret; + unsigned int fpos_offset; + unsigned int reg_offset; + + /* Suppress the cache if we're using a subrange */ + if (base) + return base; + + /* + * If we don't have a cache build one so we don't have to do a + * linear scan each time. + */ + if (regmap_debugfs_generate_cache(map) < 0) + return base; + + mutex_lock(&map->cache_lock); + /* + * This should never happen; we return above if we fail to + * allocate and we should never be in this code if there are + * no registers at all. + */ + WARN_ON(list_empty(&map->debugfs_off_cache)); + ret = base; + + /* Find the relevant block:offset */ + list_for_each_entry(c, &map->debugfs_off_cache, list) { + if (from >= c->min && from <= c->max) { + fpos_offset = from - c->min; + reg_offset = fpos_offset / map->debugfs_tot_len; + *pos = c->min + (reg_offset * map->debugfs_tot_len); + mutex_unlock(&map->cache_lock); + return c->base_reg + (reg_offset * map->reg_stride); + } + + *pos = c->max; + ret = c->max_reg; + } + mutex_unlock(&map->cache_lock); + + return ret; +} + +/* Determine the file offset where a register appears */ +static int regmap_debugfs_get_reg_offset(struct regmap *map, + unsigned int reg, + loff_t *pos) +{ + struct regmap_debugfs_off_cache *c = NULL; + unsigned int reg_offset; + int ret; + + regmap_calc_tot_len(map, NULL, 0); + ret = regmap_debugfs_generate_cache(map); + if (ret < 0) + return ret; + + mutex_lock(&map->cache_lock); + list_for_each_entry(c, &map->debugfs_off_cache, list) { + if (reg >= c->base_reg && reg <= c->max_reg) { + reg_offset = (reg - c->base_reg) / map->reg_stride; + *pos = c->min + (reg_offset * map->debugfs_tot_len); + mutex_unlock(&map->cache_lock); + return 0; + } + } + mutex_unlock(&map->cache_lock); + + return -EINVAL; +} + +static ssize_t regmap_read_debugfs(struct regmap *map, unsigned int from, + unsigned int to, char __user *user_buf, + size_t count, loff_t *ppos) +{ + size_t buf_pos = 0; + loff_t p = *ppos; + ssize_t ret; + int i; + char *buf; + unsigned int val, start_reg; + + if (*ppos < 0 || !count) + return -EINVAL; + + if (count > (PAGE_SIZE << (MAX_ORDER - 1))) + count = PAGE_SIZE << (MAX_ORDER - 1); + + buf = kzalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + regmap_calc_tot_len(map, buf, count); + + /* Work out which register we're starting at */ + start_reg = regmap_debugfs_get_dump_start(map, from, *ppos, &p); + + for (i = start_reg; i >= 0 && i <= to; + i = regmap_next_readable_reg(map, i)) { + + /* If we're in the region the user is trying to read */ + if (p >= *ppos) { + /* ...but not beyond it */ + if (buf_pos + map->debugfs_tot_len > count) + break; + + /* Format the register */ + snprintf(buf + buf_pos, count - buf_pos, "%.*x: ", + map->debugfs_reg_len, i - from); + buf_pos += map->debugfs_reg_len + 2; + + /* Format the value, write all X if we can't read */ + ret = regmap_read(map, i, &val); + if (ret == 0) + snprintf(buf + buf_pos, count - buf_pos, + "%.*x", map->debugfs_val_len, val); + else + memset(buf + buf_pos, 'X', + map->debugfs_val_len); + buf_pos += 2 * map->format.val_bytes; + + buf[buf_pos++] = '\n'; + } + p += map->debugfs_tot_len; + } + + ret = buf_pos; + + if (copy_to_user(user_buf, buf, buf_pos)) { + ret = -EFAULT; + goto out; + } + + *ppos += buf_pos; + +out: + kfree(buf); + return ret; +} + +static ssize_t regmap_data_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct regmap_qti_debugfs *debug_map = file->private_data; + loff_t reg_pos = 0; + unsigned int max_reg; + ssize_t ret; + + ret = regmap_debugfs_get_reg_offset(debug_map->regmap, + debug_map->dump_address, ®_pos); + if (ret < 0) + return ret; + + /* Treat the file position of dump_address as 0 */ + *ppos += reg_pos; + max_reg = debug_map->dump_address + + (debug_map->dump_count ? (debug_map->dump_count - 1) : 0) * + (debug_map->regmap->reg_stride ?: 1); + + ret = regmap_read_debugfs(debug_map->regmap, 0, max_reg, user_buf, + count, ppos); + if (*ppos < reg_pos) + return -EINVAL; + *ppos -= reg_pos; + + return ret; +} + +#ifdef CONFIG_REGMAP_QTI_DEBUGFS_ALLOW_WRITE + +static ssize_t regmap_data_write_file(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[32]; + size_t buf_size; + char *start = buf; + unsigned long value; + struct regmap_qti_debugfs *debug_map = file->private_data; + int ret; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + while (*start == ' ') + start++; + + if (kstrtoul(start, 16, &value)) + return -EINVAL; + + /* Userspace has been fiddling around behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + ret = regmap_write(debug_map->regmap, debug_map->dump_address, value); + if (ret < 0) + return ret; + + return buf_size; +} + +#define QTI_DEBUGFS_FILE_MODE 0600 + +#else + +#define regmap_data_write_file NULL +#define QTI_DEBUGFS_FILE_MODE 0400 + +#endif + +static const struct file_operations regmap_data_fops = { + .open = simple_open, + .read = regmap_data_read_file, + .write = regmap_data_write_file, + .llseek = default_llseek, +}; + +/** + * regmap_qti_debugfs_add() - register extra debugfs files for a regmap + * @dev: Device pointer of regmap owner + * @regmap: regmap pointer + * + * This function adds various debugfs files for the specified regmap which + * provide a mechanism for userspace to read and write regmap register values + * with easy. + * + * Returns a valid pointer on success or ERR_PTR() on failure. + */ +static struct regmap_qti_debugfs *regmap_qti_debugfs_add(struct device *dev, + struct regmap *regmap) +{ + struct regmap_qti_debugfs *debug_map = NULL; + + if (!dev || !regmap) { + pr_err("%s: dev or regmap is NULL\n", __func__); + return ERR_PTR(-EINVAL); + } + + mutex_lock(®map_qti_debugfs_lock); + list_for_each_entry(debug_map, ®map_qti_debugfs_list, list) { + if (debug_map->regmap == regmap) { + debug_map = ERR_PTR(-EINVAL); + dev_err(dev, "%s: qti debugfs files already registered for regmap\n", + __func__); + goto done; + } + } + + debug_map = kzalloc(sizeof(*debug_map), GFP_KERNEL); + if (!debug_map) { + debug_map = ERR_PTR(-ENOMEM); + goto done; + } + + debug_map->dev = dev; + debug_map->regmap = regmap; + debug_map->dump_count = 1; + + list_add(&debug_map->list, ®map_qti_debugfs_list); + + debugfs_create_x32("address", 0600, regmap->debugfs, + &debug_map->dump_address); + + debugfs_create_u32("count", 0600, regmap->debugfs, + &debug_map->dump_count); + + debugfs_create_file_unsafe("data", QTI_DEBUGFS_FILE_MODE, + regmap->debugfs, debug_map, ®map_data_fops); + +done: + mutex_unlock(®map_qti_debugfs_lock); + + return debug_map; +} + +/** + * regmap_qti_debugfs_register() - register extra debugfs files for a regmap + * @dev: Device pointer of regmap owner + * @regmap: regmap pointer + * + * This function calls regmap_qti_debugfs_add() which adds several debugfs files + * for the specified regmap which allow for userspace register read and write + * access. + * + * Returns 0 on success or an errno on failure. + */ +int regmap_qti_debugfs_register(struct device *dev, struct regmap *regmap) +{ + return PTR_ERR_OR_ZERO(regmap_qti_debugfs_add(dev, regmap)); +} +EXPORT_SYMBOL(regmap_qti_debugfs_register); + +/* regmap_qti_debugfs_lock must be held by caller. */ +static void regmap_qti_debugfs_remove(struct regmap_qti_debugfs *debug_map) +{ + struct dentry *file; + + file = debugfs_lookup("address", debug_map->regmap->debugfs); + dput(file); + debugfs_remove(file); + + file = debugfs_lookup("count", debug_map->regmap->debugfs); + dput(file); + debugfs_remove(file); + + file = debugfs_lookup("data", debug_map->regmap->debugfs); + dput(file); + debugfs_remove(file); + + list_del(&debug_map->list); + kfree(debug_map); +} + +/** + * regmap_qti_debugfs_unregister() - remove extra debugfs files associated with + * a regmap + * @regmap: regmap pointer + * + * This function removes the extra debugfs files registered for 'regmap' and + * then frees the regmap debug resources. + */ +void regmap_qti_debugfs_unregister(struct regmap *regmap) +{ + struct regmap_qti_debugfs *debug_map, *temp; + + if (IS_ERR_OR_NULL(regmap)) { + pr_err("%s: invalid regmap pointer\n", __func__); + return; + } + + mutex_lock(®map_qti_debugfs_lock); + list_for_each_entry_safe(debug_map, temp, ®map_qti_debugfs_list, + list) { + if (debug_map->regmap == regmap) { + regmap_qti_debugfs_remove(debug_map); + break; + } + } + mutex_unlock(®map_qti_debugfs_lock); +} +EXPORT_SYMBOL(regmap_qti_debugfs_unregister); + +/* regmap_qti_debugfs_lock must be held by caller. */ +static void _devm_regmap_qti_debugfs_release(struct device *dev, void *res) +{ + struct regmap_qti_debugfs *debug_map, *temp; + bool found = false; + + debug_map = *(struct regmap_qti_debugfs **)res; + list_for_each_entry(temp, ®map_qti_debugfs_list, list) { + if (temp == debug_map) { + found = true; + break; + } + } + + if (found) + regmap_qti_debugfs_remove(debug_map); +} + +static void devm_regmap_qti_debugfs_release(struct device *dev, void *res) +{ + mutex_lock(®map_qti_debugfs_lock); + _devm_regmap_qti_debugfs_release(dev, res); + mutex_unlock(®map_qti_debugfs_lock); +} + +/** + * devm_regmap_qti_debugfs_register() - resource managed version of + * regmap_qti_debugfs_register() + * @dev: Device pointer of regmap owner + * @regmap: regmap pointer + * + * This is a resource managed version of regmap_qti_debugfs_register(). + * The debugfs files added via this call are automatically removed on driver + * detach. + * + * Returns 0 on success or an errno on failure. + */ +int devm_regmap_qti_debugfs_register(struct device *dev, struct regmap *regmap) +{ + struct regmap_qti_debugfs *debug_map; + struct regmap_qti_debugfs **ptr; + + ptr = devres_alloc(devm_regmap_qti_debugfs_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + debug_map = regmap_qti_debugfs_add(dev, regmap); + if (IS_ERR(debug_map)) { + devres_free(ptr); + return PTR_ERR(debug_map); + } + + *ptr = debug_map; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL(devm_regmap_qti_debugfs_register); + +static int devm_regmap_qti_debugfs_match(struct device *dev, void *res, + void *data) +{ + struct regmap_qti_debugfs **debug_map = res; + + if (!debug_map || !*debug_map) { + WARN_ON(!debug_map || !*debug_map); + return 0; + } + + return *debug_map == data; +} + +/** + * devm_regmap_qti_debugfs_unregister() - resource managed version of + * regmap_qti_debugfs_unregister() + * @regmap: regmap pointer + * + * Deallocate the debug regmap data allocated for 'regmap' with + * devm_regmap_qti_debugfs_register(). Normally this function will not + * need to be called and the resource management code will ensure that the + * resource is freed. + */ +void devm_regmap_qti_debugfs_unregister(struct regmap *regmap) +{ + struct regmap_qti_debugfs *debug_map, *temp; + + if (IS_ERR_OR_NULL(regmap)) { + pr_err("%s: invalid regmap pointer\n", __func__); + return; + } + + mutex_lock(®map_qti_debugfs_lock); + list_for_each_entry_safe(debug_map, temp, ®map_qti_debugfs_list, + list) { + if (debug_map->regmap == regmap) + devres_release(debug_map->dev, + _devm_regmap_qti_debugfs_release, + devm_regmap_qti_debugfs_match, debug_map); + } + mutex_unlock(®map_qti_debugfs_lock); +} +EXPORT_SYMBOL(devm_regmap_qti_debugfs_unregister); + +MODULE_DESCRIPTION("Regmap QTI debugfs library"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 3b59456f5545..9dd27146af29 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1066,6 +1066,17 @@ config MFD_PM8XXX Say M here if you want to include support for PM8xxx chips as a module. This will build a module called "pm8xxx-core". +config MFD_I2C_PMIC + tristate "QTI I2C PMIC support" + depends on I2C && OF + select IRQ_DOMAIN + select REGMAP_I2C + help + This enables support for controlling Qualcomm Technologies, Inc. + PMICs over I2C. The driver controls interrupts, and provides register + access for all of the device's peripherals. Some QTI PMIC chips + support communication over both I2C and SPMI. + config MFD_QCOM_RPM tristate "Qualcomm Resource Power Manager (RPM)" depends on ARCH_QCOM && OF diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 858cacf659d6..8434513de7db 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -198,6 +198,7 @@ obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o obj-$(CONFIG_MFD_PM8XXX) += qcom-pm8xxx.o ssbi.o +obj-$(CONFIG_MFD_I2C_PMIC) += qcom-i2c-pmic.o obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o diff --git a/drivers/mfd/qcom-i2c-pmic.c b/drivers/mfd/qcom-i2c-pmic.c new file mode 100644 index 000000000000..8530f3f47a97 --- /dev/null +++ b/drivers/mfd/qcom-i2c-pmic.c @@ -0,0 +1,813 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "I2C PMIC: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define I2C_INTR_STATUS_BASE 0x0550 +#define INT_RT_STS_OFFSET 0x10 +#define INT_SET_TYPE_OFFSET 0x11 +#define INT_POL_HIGH_OFFSET 0x12 +#define INT_POL_LOW_OFFSET 0x13 +#define INT_LATCHED_CLR_OFFSET 0x14 +#define INT_EN_SET_OFFSET 0x15 +#define INT_EN_CLR_OFFSET 0x16 +#define INT_LATCHED_STS_OFFSET 0x18 +#define INT_PENDING_STS_OFFSET 0x19 +#define INT_MID_SEL_OFFSET 0x1A +#define INT_MID_SEL_MASK GENMASK(1, 0) +#define INT_PRIORITY_OFFSET 0x1B +#define INT_PRIORITY_BIT BIT(0) + +enum { + IRQ_SET_TYPE = 0, + IRQ_POL_HIGH, + IRQ_POL_LOW, + IRQ_LATCHED_CLR, /* not needed but makes life easy */ + IRQ_EN_SET, + IRQ_MAX_REGS, +}; + +struct i2c_pmic_periph { + void *data; + u16 addr; + u8 cached[IRQ_MAX_REGS]; + u8 synced[IRQ_MAX_REGS]; + u8 wake; + struct mutex lock; +}; + +struct i2c_pmic { + struct device *dev; + struct regmap *regmap; + struct irq_domain *domain; + struct i2c_pmic_periph *periph; + struct pinctrl *pinctrl; + struct mutex irq_complete; + const char *pinctrl_name; + int num_periphs; + int summary_irq; + bool resume_completed; + bool irq_waiting; + bool toggle_stat; +}; + +static void i2c_pmic_irq_bus_lock(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + mutex_lock(&periph->lock); +} + +static void i2c_pmic_sync_type_polarity(struct i2c_pmic *chip, + struct i2c_pmic_periph *periph) +{ + int rc; + + /* did any irq type change? */ + if (periph->cached[IRQ_SET_TYPE] ^ periph->synced[IRQ_SET_TYPE]) { + rc = regmap_write(chip->regmap, + periph->addr | INT_SET_TYPE_OFFSET, + periph->cached[IRQ_SET_TYPE]); + if (rc < 0) { + pr_err("Couldn't set periph 0x%04x irqs 0x%02x type rc=%d\n", + periph->addr, periph->cached[IRQ_SET_TYPE], rc); + return; + } + + periph->synced[IRQ_SET_TYPE] = periph->cached[IRQ_SET_TYPE]; + } + + /* did any polarity high change? */ + if (periph->cached[IRQ_POL_HIGH] ^ periph->synced[IRQ_POL_HIGH]) { + rc = regmap_write(chip->regmap, + periph->addr | INT_POL_HIGH_OFFSET, + periph->cached[IRQ_POL_HIGH]); + if (rc < 0) { + pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity high rc=%d\n", + periph->addr, periph->cached[IRQ_POL_HIGH], rc); + return; + } + + periph->synced[IRQ_POL_HIGH] = periph->cached[IRQ_POL_HIGH]; + } + + /* did any polarity low change? */ + if (periph->cached[IRQ_POL_LOW] ^ periph->synced[IRQ_POL_LOW]) { + rc = regmap_write(chip->regmap, + periph->addr | INT_POL_LOW_OFFSET, + periph->cached[IRQ_POL_LOW]); + if (rc < 0) { + pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity low rc=%d\n", + periph->addr, periph->cached[IRQ_POL_LOW], rc); + return; + } + + periph->synced[IRQ_POL_LOW] = periph->cached[IRQ_POL_LOW]; + } +} + +static void i2c_pmic_sync_enable(struct i2c_pmic *chip, + struct i2c_pmic_periph *periph) +{ + u8 en_set, en_clr; + int rc; + + /* determine which irqs were enabled and which were disabled */ + en_clr = periph->synced[IRQ_EN_SET] & ~periph->cached[IRQ_EN_SET]; + en_set = ~periph->synced[IRQ_EN_SET] & periph->cached[IRQ_EN_SET]; + + /* were any irqs disabled? */ + if (en_clr) { + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_CLR_OFFSET, en_clr); + if (rc < 0) { + pr_err("Couldn't disable periph 0x%04x irqs 0x%02x rc=%d\n", + periph->addr, en_clr, rc); + return; + } + } + + /* were any irqs enabled? */ + if (en_set) { + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_SET_OFFSET, en_set); + if (rc < 0) { + pr_err("Couldn't enable periph 0x%04x irqs 0x%02x rc=%d\n", + periph->addr, en_set, rc); + return; + } + } + + /* irq enabled status was written to hardware */ + periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET]; +} + +static void i2c_pmic_irq_bus_sync_unlock(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + struct i2c_pmic *chip = periph->data; + + i2c_pmic_sync_type_polarity(chip, periph); + i2c_pmic_sync_enable(chip, periph); + mutex_unlock(&periph->lock); +} + +static void i2c_pmic_irq_disable(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + periph->cached[IRQ_EN_SET] &= ~d->hwirq & 0xFF; +} + +static void i2c_pmic_irq_enable(struct irq_data *d) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + periph->cached[IRQ_EN_SET] |= d->hwirq & 0xFF; +} + +static int i2c_pmic_irq_set_type(struct irq_data *d, unsigned int irq_type) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + switch (irq_type) { + case IRQ_TYPE_EDGE_RISING: + periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF; + break; + case IRQ_TYPE_EDGE_FALLING: + periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF; + break; + case IRQ_TYPE_EDGE_BOTH: + periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF; + break; + case IRQ_TYPE_LEVEL_HIGH: + periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF; + break; + case IRQ_TYPE_LEVEL_LOW: + periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF; + periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF; + break; + default: + pr_err("irq type 0x%04x is not supported\n", irq_type); + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int i2c_pmic_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d); + + if (on) + periph->wake |= d->hwirq & 0xFF; + else + periph->wake &= ~d->hwirq & 0xFF; + + return 0; +} +#else +#define i2c_pmic_irq_set_wake NULL +#endif + +static struct irq_chip i2c_pmic_irq_chip = { + .name = "i2c_pmic_irq_chip", + .irq_bus_lock = i2c_pmic_irq_bus_lock, + .irq_bus_sync_unlock = i2c_pmic_irq_bus_sync_unlock, + .irq_disable = i2c_pmic_irq_disable, + .irq_enable = i2c_pmic_irq_enable, + .irq_set_type = i2c_pmic_irq_set_type, + .irq_set_wake = i2c_pmic_irq_set_wake, +}; + +static struct i2c_pmic_periph *i2c_pmic_find_periph(struct i2c_pmic *chip, + irq_hw_number_t hwirq) +{ + int i; + + for (i = 0; i < chip->num_periphs; i++) + if (chip->periph[i].addr == (hwirq & 0xFF00)) + return &chip->periph[i]; + + pr_err_ratelimited("Couldn't find periph struct for hwirq 0x%04lx\n", + hwirq); + return NULL; +} + +static int i2c_pmic_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct i2c_pmic *chip = d->host_data; + struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq); + + if (!periph) + return -ENODEV; + + irq_set_chip_data(virq, periph); + irq_set_chip_and_handler(virq, &i2c_pmic_irq_chip, handle_level_irq); + irq_set_nested_thread(virq, 1); + irq_set_noprobe(virq); + return 0; +} + +static int i2c_pmic_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, const u32 *intspec, + unsigned int intsize, unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (intsize != 3) + return -EINVAL; + + if (intspec[0] > 0xFF || intspec[1] > 0x7 || + intspec[2] > IRQ_TYPE_SENSE_MASK) + return -EINVAL; + + /* + * Interrupt specifiers are triplets + * + * + * peripheral-address - The base address of the peripheral + * irq-number - The zero based bit position of the peripheral's + * interrupt registers corresponding to the irq + * where the LSB is 0 and the MSB is 7 + * IRQ_TYPE_* - Please refer to linux/irq.h + */ + *out_hwirq = intspec[0] << 8 | BIT(intspec[1]); + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static const struct irq_domain_ops i2c_pmic_domain_ops = { + .map = i2c_pmic_domain_map, + .xlate = i2c_pmic_domain_xlate, +}; + +static void i2c_pmic_irq_ack_now(struct i2c_pmic *chip, u16 hwirq) +{ + int rc; + + rc = regmap_write(chip->regmap, + (hwirq & 0xFF00) | INT_LATCHED_CLR_OFFSET, + hwirq & 0xFF); + if (rc < 0) + pr_err_ratelimited("Couldn't ack 0x%04x rc=%d\n", hwirq, rc); +} + +static void i2c_pmic_irq_disable_now(struct i2c_pmic *chip, u16 hwirq) +{ + struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq); + int rc; + + if (!periph) + return; + + mutex_lock(&periph->lock); + periph->cached[IRQ_EN_SET] &= ~hwirq & 0xFF; + + rc = regmap_write(chip->regmap, + (hwirq & 0xFF00) | INT_EN_CLR_OFFSET, + hwirq & 0xFF); + if (rc < 0) { + pr_err_ratelimited("Couldn't disable irq 0x%04x rc=%d\n", + hwirq, rc); + goto unlock; + } + + periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET]; + +unlock: + mutex_unlock(&periph->lock); +} + +static void i2c_pmic_periph_status_handler(struct i2c_pmic *chip, + u16 periph_address, u8 periph_status) +{ + unsigned int hwirq, virq; + int i; + + while (periph_status) { + i = ffs(periph_status) - 1; + periph_status &= ~BIT(i); + hwirq = periph_address | BIT(i); + virq = irq_find_mapping(chip->domain, hwirq); + if (virq == 0) { + pr_err_ratelimited("Couldn't find mapping; disabling 0x%04x\n", + hwirq); + i2c_pmic_irq_disable_now(chip, hwirq); + continue; + } + + handle_nested_irq(virq); + i2c_pmic_irq_ack_now(chip, hwirq); + } +} + +static void i2c_pmic_summary_status_handler(struct i2c_pmic *chip, + struct i2c_pmic_periph *periph, + u8 summary_status) +{ + unsigned int periph_status; + int rc, i; + + while (summary_status) { + i = ffs(summary_status) - 1; + summary_status &= ~BIT(i); + + rc = regmap_read(chip->regmap, + periph[i].addr | INT_LATCHED_STS_OFFSET, + &periph_status); + if (rc < 0) { + pr_err_ratelimited("Couldn't read 0x%04x | INT_LATCHED_STS rc=%d\n", + periph[i].addr, rc); + continue; + } + + i2c_pmic_periph_status_handler(chip, periph[i].addr, + periph_status); + } +} + +static irqreturn_t i2c_pmic_irq_handler(int irq, void *dev_id) +{ + struct i2c_pmic *chip = dev_id; + struct i2c_pmic_periph *periph; + unsigned int summary_status; + int rc, i; + + mutex_lock(&chip->irq_complete); + chip->irq_waiting = true; + if (!chip->resume_completed) { + pr_debug("IRQ triggered before device-resume\n"); + disable_irq_nosync(irq); + mutex_unlock(&chip->irq_complete); + return IRQ_HANDLED; + } + chip->irq_waiting = false; + + for (i = 0; i < DIV_ROUND_UP(chip->num_periphs, BITS_PER_BYTE); i++) { + rc = regmap_read(chip->regmap, I2C_INTR_STATUS_BASE + i, + &summary_status); + if (rc < 0) { + pr_err_ratelimited("Couldn't read I2C_INTR_STATUS%d rc=%d\n", + i, rc); + continue; + } + + if (summary_status == 0) + continue; + + periph = &chip->periph[i * 8]; + i2c_pmic_summary_status_handler(chip, periph, summary_status); + } + + mutex_unlock(&chip->irq_complete); + + return IRQ_HANDLED; +} + +static int i2c_pmic_parse_dt(struct i2c_pmic *chip) +{ + struct device_node *node = chip->dev->of_node; + int rc, i; + u32 temp; + + if (!node) { + pr_err("missing device tree\n"); + return -EINVAL; + } + + chip->num_periphs = of_property_count_u32_elems(node, + "qcom,periph-map"); + if (chip->num_periphs < 0) { + pr_err("missing qcom,periph-map property rc=%d\n", + chip->num_periphs); + return chip->num_periphs; + } + + if (chip->num_periphs == 0) { + pr_err("qcom,periph-map must contain at least one address\n"); + return -EINVAL; + } + + chip->periph = devm_kcalloc(chip->dev, chip->num_periphs, + sizeof(*chip->periph), GFP_KERNEL); + if (!chip->periph) + return -ENOMEM; + + for (i = 0; i < chip->num_periphs; i++) { + rc = of_property_read_u32_index(node, "qcom,periph-map", + i, &temp); + if (rc < 0) { + pr_err("Couldn't read qcom,periph-map[%d] rc=%d\n", + i, rc); + return rc; + } + + chip->periph[i].addr = (u16)(temp << 8); + chip->periph[i].data = chip; + mutex_init(&chip->periph[i].lock); + } + + of_property_read_string(node, "pinctrl-names", &chip->pinctrl_name); + + chip->toggle_stat = of_property_read_bool(node, + "qcom,enable-toggle-stat"); + + return rc; +} + +#define MAX_I2C_RETRIES 3 +static int i2c_pmic_read(struct regmap *map, unsigned int reg, void *val, + size_t val_count) +{ + int rc, retries = 0; + + do { + rc = regmap_bulk_read(map, reg, val, val_count); + } while (rc == -ENOTCONN && retries++ < MAX_I2C_RETRIES); + + if (retries > 1) + pr_err("i2c_pmic_read failed for %d retries, rc = %d\n", + retries - 1, rc); + + return rc; +} + +static int i2c_pmic_determine_initial_status(struct i2c_pmic *chip) +{ + int rc, i; + + for (i = 0; i < chip->num_periphs; i++) { + rc = i2c_pmic_read(chip->regmap, + chip->periph[i].addr | INT_SET_TYPE_OFFSET, + chip->periph[i].cached, IRQ_MAX_REGS); + if (rc < 0) { + pr_err("Couldn't read irq data rc=%d\n", rc); + return rc; + } + + memcpy(chip->periph[i].synced, chip->periph[i].cached, + IRQ_MAX_REGS * sizeof(*chip->periph[i].synced)); + } + + return 0; +} + +#define INT_TEST_OFFSET 0xE0 +#define INT_TEST_MODE_EN_BIT BIT(7) +#define INT_TEST_VAL_OFFSET 0xE1 +#define INT_0_BIT BIT(0) +static int i2c_pmic_toggle_stat(struct i2c_pmic *chip) +{ + int rc = 0, i; + + if (!chip->toggle_stat || !chip->num_periphs) + return 0; + + rc = regmap_write(chip->regmap, + chip->periph[0].addr | INT_EN_SET_OFFSET, + INT_0_BIT); + if (rc < 0) { + pr_err("Couldn't write to int_en_set rc=%d\n", rc); + return rc; + } + + rc = regmap_write(chip->regmap, chip->periph[0].addr | INT_TEST_OFFSET, + INT_TEST_MODE_EN_BIT); + if (rc < 0) { + pr_err("Couldn't write to int_test rc=%d\n", rc); + return rc; + } + + for (i = 0; i < 5; i++) { + rc = regmap_write(chip->regmap, + chip->periph[0].addr | INT_TEST_VAL_OFFSET, + INT_0_BIT); + if (rc < 0) { + pr_err("Couldn't write to int_test_val rc=%d\n", rc); + goto exit; + } + + usleep_range(5000, 5500); + + rc = regmap_write(chip->regmap, + chip->periph[0].addr | INT_TEST_VAL_OFFSET, + 0); + if (rc < 0) { + pr_err("Couldn't write to int_test_val rc=%d\n", rc); + goto exit; + } + + rc = regmap_write(chip->regmap, + chip->periph[0].addr | INT_LATCHED_CLR_OFFSET, + INT_0_BIT); + if (rc < 0) { + pr_err("Couldn't write to int_latched_clr rc=%d\n", rc); + goto exit; + } + + usleep_range(5000, 5500); + } +exit: + regmap_write(chip->regmap, chip->periph[0].addr | INT_TEST_OFFSET, 0); + regmap_write(chip->regmap, chip->periph[0].addr | INT_EN_CLR_OFFSET, + INT_0_BIT); + + return rc; +} + +static struct regmap_config i2c_pmic_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xFFFF, +}; + +static int i2c_pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_pmic *chip; + int rc = 0; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &client->dev; + chip->regmap = devm_regmap_init_i2c(client, &i2c_pmic_regmap_config); + if (!chip->regmap) + return -ENODEV; + + devm_regmap_qti_debugfs_register(chip->dev, chip->regmap); + + i2c_set_clientdata(client, chip); + if (!of_property_read_bool(chip->dev->of_node, "interrupt-controller")) + goto probe_children; + + chip->domain = irq_domain_add_tree(client->dev.of_node, + &i2c_pmic_domain_ops, chip); + if (!chip->domain) { + rc = -ENOMEM; + goto cleanup; + } + + rc = i2c_pmic_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + goto cleanup; + } + + rc = i2c_pmic_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", rc); + goto cleanup; + } + + if (chip->pinctrl_name) { + chip->pinctrl = devm_pinctrl_get_select(chip->dev, + chip->pinctrl_name); + if (IS_ERR(chip->pinctrl)) { + pr_err("Couldn't select %s pinctrl rc=%ld\n", + chip->pinctrl_name, PTR_ERR(chip->pinctrl)); + rc = PTR_ERR(chip->pinctrl); + goto cleanup; + } + } + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + rc = i2c_pmic_toggle_stat(chip); + if (rc < 0) { + pr_err("Couldn't toggle stat rc=%d\n", rc); + goto cleanup; + } + + rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, + i2c_pmic_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + "i2c_pmic_stat_irq", chip); + if (rc < 0) { + pr_err("Couldn't request irq %d rc=%d\n", client->irq, rc); + goto cleanup; + } + + chip->summary_irq = client->irq; + enable_irq_wake(client->irq); + +probe_children: + of_platform_populate(chip->dev->of_node, NULL, NULL, chip->dev); + pr_info("I2C PMIC probe successful\n"); + return rc; + +cleanup: + if (chip->domain) + irq_domain_remove(chip->domain); + i2c_set_clientdata(client, NULL); + return rc; +} + +static int i2c_pmic_remove(struct i2c_client *client) +{ + struct i2c_pmic *chip = i2c_get_clientdata(client); + + of_platform_depopulate(chip->dev); + if (chip->domain) + irq_domain_remove(chip->domain); + i2c_set_clientdata(client, NULL); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int i2c_pmic_suspend_noirq(struct device *dev) +{ + struct i2c_pmic *chip = dev_get_drvdata(dev); + + if (chip->irq_waiting) { + pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); + return -EBUSY; + } + return 0; +} + +static int i2c_pmic_suspend(struct device *dev) +{ + struct i2c_pmic *chip = dev_get_drvdata(dev); + struct i2c_pmic_periph *periph; + int rc = 0, i; + + for (i = 0; i < chip->num_periphs; i++) { + periph = &chip->periph[i]; + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_CLR_OFFSET, 0xFF); + if (rc < 0) { + pr_err_ratelimited("Couldn't clear 0x%04x irqs rc=%d\n", + periph->addr, rc); + continue; + } + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_SET_OFFSET, + periph->wake); + if (rc < 0) + pr_err_ratelimited("Couldn't enable 0x%04x wake irqs 0x%02x rc=%d\n", + periph->addr, periph->wake, rc); + } + if (!rc) { + mutex_lock(&chip->irq_complete); + chip->resume_completed = false; + mutex_unlock(&chip->irq_complete); + } + + return rc; +} + +static int i2c_pmic_resume(struct device *dev) +{ + struct i2c_pmic *chip = dev_get_drvdata(dev); + struct i2c_pmic_periph *periph; + int rc = 0, i; + + for (i = 0; i < chip->num_periphs; i++) { + periph = &chip->periph[i]; + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_CLR_OFFSET, 0xFF); + if (rc < 0) { + pr_err("Couldn't clear 0x%04x irqs rc=%d\n", + periph->addr, rc); + continue; + } + + rc = regmap_write(chip->regmap, + periph->addr | INT_EN_SET_OFFSET, + periph->synced[IRQ_EN_SET]); + if (rc < 0) + pr_err("Couldn't restore 0x%04x synced irqs 0x%02x rc=%d\n", + periph->addr, periph->synced[IRQ_EN_SET], rc); + } + + mutex_lock(&chip->irq_complete); + chip->resume_completed = true; + if (chip->irq_waiting) { + mutex_unlock(&chip->irq_complete); + /* irq was pending, call the handler */ + i2c_pmic_irq_handler(chip->summary_irq, chip); + enable_irq(chip->summary_irq); + } else { + mutex_unlock(&chip->irq_complete); + } + + return rc; +} +#else +static int i2c_pmic_suspend(struct device *dev) +{ + return 0; +} +static int i2c_pmic_resume(struct device *dev) +{ + return 0; +} +static int i2c_pmic_suspend_noirq(struct device *dev) +{ + return 0 +} +#endif +static const struct dev_pm_ops i2c_pmic_pm_ops = { + .suspend = i2c_pmic_suspend, + .suspend_noirq = i2c_pmic_suspend_noirq, + .resume = i2c_pmic_resume, +}; + +static const struct of_device_id i2c_pmic_match_table[] = { + { .compatible = "qcom,i2c-pmic", }, + { }, +}; + +static const struct i2c_device_id i2c_pmic_id[] = { + { "i2c-pmic", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, i2c_pmic_id); + +static struct i2c_driver i2c_pmic_driver = { + .driver = { + .name = "i2c_pmic", + .pm = &i2c_pmic_pm_ops, + .of_match_table = i2c_pmic_match_table, + }, + .probe = i2c_pmic_probe, + .remove = i2c_pmic_remove, + .id_table = i2c_pmic_id, +}; + +module_i2c_driver(i2c_pmic_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:i2c_pmic"); diff --git a/drivers/mfd/qcom-spmi-pmic.c b/drivers/mfd/qcom-spmi-pmic.c index f86d3514e08d..92a9cd8ddc7c 100644 --- a/drivers/mfd/qcom-spmi-pmic.c +++ b/drivers/mfd/qcom-spmi-pmic.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2014-2015, 2017-2019, The Linux Foundation. + * Copyright (c) 2014-2015, 2017-2020, The Linux Foundation. * All rights reserved. */ @@ -9,6 +9,7 @@ #include #include #include +#include #define PMIC_REV2 0x101 #define PMIC_REV3 0x102 @@ -162,6 +163,8 @@ static int pmic_spmi_probe(struct spmi_device *sdev) if (IS_ERR(regmap)) return PTR_ERR(regmap); + devm_regmap_qti_debugfs_register(&sdev->dev, regmap); + /* Only the first slave id for a PMIC contains this information */ if (sdev->usid % 2 == 0) pmic_spmi_show_revid(regmap, &sdev->dev); diff --git a/include/linux/qti-regmap-debugfs.h b/include/linux/qti-regmap-debugfs.h new file mode 100644 index 000000000000..16c83172cda0 --- /dev/null +++ b/include/linux/qti-regmap-debugfs.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef _LINUX_QTI_REGMAP_DEBUGFS_H_ +#define _LINUX_QTI_REGMAP_DEBUGFS_H_ + +#include +#include + +#if IS_ENABLED(CONFIG_REGMAP_QTI_DEBUGFS) + +int regmap_qti_debugfs_register(struct device *dev, struct regmap *regmap); +void regmap_qti_debugfs_unregister(struct regmap *regmap); +int devm_regmap_qti_debugfs_register(struct device *dev, struct regmap *regmap); +void devm_regmap_qti_debugfs_unregister(struct regmap *regmap); + +#else + +static inline int regmap_qti_debugfs_register(struct device *dev, + struct regmap *regmap) +{ return 0; } +static inline void regmap_qti_debugfs_unregister(struct regmap *regmap) +{ } +static inline int devm_regmap_qti_debugfs_register(struct device *dev, + struct regmap *regmap) +{ return 0; } +static inline void devm_regmap_qti_debugfs_unregister(struct regmap *regmap) +{ } + +#endif +#endif