From 40a3a0f2ba633fa219f69aef0fd0dcdd6e5756b0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 19 Nov 2016 16:15:18 +0100 Subject: mfd: qcom-pm8xxx: Clean up PM8XXX namespace The Kconfig and file naming for the PM8xxx driver is totally confusing: - Kconfig options MFD_PM8XXX and MFD_PM8921_CORE, some in-kernel users depending on or selecting either at random. - A driver file named pm8921-core.c even if it is indeed used by the whole PM8xxx family of chips. - An irqchip named pm8xxx since it was (I guess) realized that the driver was generic for all pm8xxx PMICs. As I may want to add support for PM8901 this is starting to get really messy. Fix this situation by: - Remove the MFD_PM8921_CORE symbol and rely solely on MFD_PM8XXX and convert all users, including LEDs Kconfig and ARM defconfigs for qcom and multi_v7 to use that single symbol. - Renaming the driver to qcom-pm8xxx.c to fit along the two other qcom* prefixed drivers. - Rename functions withing the driver from 8921 to 8xxx to indicate it is generic. - Just drop the =m config from the pxa_defconfig, I have no clue why it is even there, it is not a Qualcomm platform. (Possibly older Kconfig noise from saveconfig.) Cc: Stephen Boyd Cc: Neil Armstrong Cc: Abhijeet Dharmapurikar Signed-off-by: Linus Walleij Reviewed-by: Andy Gross Acked-by: Bjorn Andersson Acked-by: Jacek Anaszewski Acked-by: Arnd Bergmann Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 2 +- drivers/mfd/Kconfig | 14 +- drivers/mfd/Makefile | 2 +- drivers/mfd/pm8921-core.c | 429 ---------------------------------------------- drivers/mfd/qcom-pm8xxx.c | 429 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 436 insertions(+), 440 deletions(-) delete mode 100644 drivers/mfd/pm8921-core.c create mode 100644 drivers/mfd/qcom-pm8xxx.c (limited to 'drivers') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 7a628c6516f6..86bb1515a00e 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -645,7 +645,7 @@ config LEDS_VERSATILE config LEDS_PM8058 tristate "LED Support for the Qualcomm PM8058 PMIC" - depends on MFD_PM8921_CORE + depends on MFD_PM8XXX depends on LEDS_CLASS help Choose this option if you want to use the LED drivers in diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df6442ba2b..1ed0584f494e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -756,24 +756,20 @@ config UCB1400_CORE module will be called ucb1400_core. config MFD_PM8XXX - tristate - -config MFD_PM8921_CORE - tristate "Qualcomm PM8921 PMIC chip" + tristate "Qualcomm PM8xxx PMIC chips driver" depends on (ARM || HEXAGON) select IRQ_DOMAIN select MFD_CORE - select MFD_PM8XXX select REGMAP help If you say yes to this option, support will be included for the - built-in PM8921 PMIC chip. + built-in PM8xxx PMIC chips. - This is required if your board has a PM8921 and uses its features, + This is required if your board has a PM8xxx and uses its features, such as: MPPs, GPIOs, regulators, interrupts, and PWM. - Say M here if you want to include support for PM8921 chip as a module. - This will build a module called "pm8921-core". + 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_QCOM_RPM tristate "Qualcomm Resource Power Manager (RPM)" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 9834e669d985..7bb5a50127cb 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -172,7 +172,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_PM8921_CORE) += pm8921-core.o ssbi.o +obj-$(CONFIG_MFD_PM8XXX) += qcom-pm8xxx.o ssbi.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/pm8921-core.c b/drivers/mfd/pm8921-core.c deleted file mode 100644 index 0e3a2ea25942..000000000000 --- a/drivers/mfd/pm8921-core.c +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#define pr_fmt(fmt) "%s: " fmt, __func__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SSBI_REG_ADDR_IRQ_BASE 0x1BB - -#define SSBI_REG_ADDR_IRQ_ROOT (SSBI_REG_ADDR_IRQ_BASE + 0) -#define SSBI_REG_ADDR_IRQ_M_STATUS1 (SSBI_REG_ADDR_IRQ_BASE + 1) -#define SSBI_REG_ADDR_IRQ_M_STATUS2 (SSBI_REG_ADDR_IRQ_BASE + 2) -#define SSBI_REG_ADDR_IRQ_M_STATUS3 (SSBI_REG_ADDR_IRQ_BASE + 3) -#define SSBI_REG_ADDR_IRQ_M_STATUS4 (SSBI_REG_ADDR_IRQ_BASE + 4) -#define SSBI_REG_ADDR_IRQ_BLK_SEL (SSBI_REG_ADDR_IRQ_BASE + 5) -#define SSBI_REG_ADDR_IRQ_IT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 6) -#define SSBI_REG_ADDR_IRQ_CONFIG (SSBI_REG_ADDR_IRQ_BASE + 7) -#define SSBI_REG_ADDR_IRQ_RT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 8) - -#define PM_IRQF_LVL_SEL 0x01 /* level select */ -#define PM_IRQF_MASK_FE 0x02 /* mask falling edge */ -#define PM_IRQF_MASK_RE 0x04 /* mask rising edge */ -#define PM_IRQF_CLR 0x08 /* clear interrupt */ -#define PM_IRQF_BITS_MASK 0x70 -#define PM_IRQF_BITS_SHIFT 4 -#define PM_IRQF_WRITE 0x80 - -#define PM_IRQF_MASK_ALL (PM_IRQF_MASK_FE | \ - PM_IRQF_MASK_RE) - -#define REG_HWREV 0x002 /* PMIC4 revision */ -#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ - -#define PM8921_NR_IRQS 256 - -struct pm_irq_chip { - struct regmap *regmap; - spinlock_t pm_irq_lock; - struct irq_domain *irqdomain; - unsigned int num_irqs; - unsigned int num_blocks; - unsigned int num_masters; - u8 config[0]; -}; - -static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, unsigned int bp, - unsigned int *ip) -{ - int rc; - - spin_lock(&chip->pm_irq_lock); - rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); - if (rc) { - pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); - goto bail; - } - - rc = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_IT_STATUS, ip); - if (rc) - pr_err("Failed Reading Status rc=%d\n", rc); -bail: - spin_unlock(&chip->pm_irq_lock); - return rc; -} - -static int -pm8xxx_config_irq(struct pm_irq_chip *chip, unsigned int bp, unsigned int cp) -{ - int rc; - - spin_lock(&chip->pm_irq_lock); - rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); - if (rc) { - pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); - goto bail; - } - - cp |= PM_IRQF_WRITE; - rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_CONFIG, cp); - if (rc) - pr_err("Failed Configuring IRQ rc=%d\n", rc); -bail: - spin_unlock(&chip->pm_irq_lock); - return rc; -} - -static int pm8xxx_irq_block_handler(struct pm_irq_chip *chip, int block) -{ - int pmirq, irq, i, ret = 0; - unsigned int bits; - - ret = pm8xxx_read_block_irq(chip, block, &bits); - if (ret) { - pr_err("Failed reading %d block ret=%d", block, ret); - return ret; - } - if (!bits) { - pr_err("block bit set in master but no irqs: %d", block); - return 0; - } - - /* Check IRQ bits */ - for (i = 0; i < 8; i++) { - if (bits & (1 << i)) { - pmirq = block * 8 + i; - irq = irq_find_mapping(chip->irqdomain, pmirq); - generic_handle_irq(irq); - } - } - return 0; -} - -static int pm8xxx_irq_master_handler(struct pm_irq_chip *chip, int master) -{ - unsigned int blockbits; - int block_number, i, ret = 0; - - ret = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_M_STATUS1 + master, - &blockbits); - if (ret) { - pr_err("Failed to read master %d ret=%d\n", master, ret); - return ret; - } - if (!blockbits) { - pr_err("master bit set in root but no blocks: %d", master); - return 0; - } - - for (i = 0; i < 8; i++) - if (blockbits & (1 << i)) { - block_number = master * 8 + i; /* block # */ - ret |= pm8xxx_irq_block_handler(chip, block_number); - } - return ret; -} - -static void pm8xxx_irq_handler(struct irq_desc *desc) -{ - struct pm_irq_chip *chip = irq_desc_get_handler_data(desc); - struct irq_chip *irq_chip = irq_desc_get_chip(desc); - unsigned int root; - int i, ret, masters = 0; - - chained_irq_enter(irq_chip, desc); - - ret = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_ROOT, &root); - if (ret) { - pr_err("Can't read root status ret=%d\n", ret); - return; - } - - /* on pm8xxx series masters start from bit 1 of the root */ - masters = root >> 1; - - /* Read allowed masters for blocks. */ - for (i = 0; i < chip->num_masters; i++) - if (masters & (1 << i)) - pm8xxx_irq_master_handler(chip, i); - - chained_irq_exit(irq_chip, desc); -} - -static void pm8xxx_irq_mask_ack(struct irq_data *d) -{ - struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); - unsigned int pmirq = irqd_to_hwirq(d); - u8 block, config; - - block = pmirq / 8; - - config = chip->config[pmirq] | PM_IRQF_MASK_ALL | PM_IRQF_CLR; - pm8xxx_config_irq(chip, block, config); -} - -static void pm8xxx_irq_unmask(struct irq_data *d) -{ - struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); - unsigned int pmirq = irqd_to_hwirq(d); - u8 block, config; - - block = pmirq / 8; - - config = chip->config[pmirq]; - pm8xxx_config_irq(chip, block, config); -} - -static int pm8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) -{ - struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); - unsigned int pmirq = irqd_to_hwirq(d); - int irq_bit; - u8 block, config; - - block = pmirq / 8; - irq_bit = pmirq % 8; - - chip->config[pmirq] = (irq_bit << PM_IRQF_BITS_SHIFT) - | PM_IRQF_MASK_ALL; - if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { - if (flow_type & IRQF_TRIGGER_RISING) - chip->config[pmirq] &= ~PM_IRQF_MASK_RE; - if (flow_type & IRQF_TRIGGER_FALLING) - chip->config[pmirq] &= ~PM_IRQF_MASK_FE; - } else { - chip->config[pmirq] |= PM_IRQF_LVL_SEL; - - if (flow_type & IRQF_TRIGGER_HIGH) - chip->config[pmirq] &= ~PM_IRQF_MASK_RE; - else - chip->config[pmirq] &= ~PM_IRQF_MASK_FE; - } - - config = chip->config[pmirq] | PM_IRQF_CLR; - return pm8xxx_config_irq(chip, block, config); -} - -static int pm8xxx_irq_get_irqchip_state(struct irq_data *d, - enum irqchip_irq_state which, - bool *state) -{ - struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); - unsigned int pmirq = irqd_to_hwirq(d); - unsigned int bits; - int irq_bit; - u8 block; - int rc; - - if (which != IRQCHIP_STATE_LINE_LEVEL) - return -EINVAL; - - block = pmirq / 8; - irq_bit = pmirq % 8; - - spin_lock(&chip->pm_irq_lock); - rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, block); - if (rc) { - pr_err("Failed Selecting Block %d rc=%d\n", block, rc); - goto bail; - } - - rc = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits); - if (rc) { - pr_err("Failed Reading Status rc=%d\n", rc); - goto bail; - } - - *state = !!(bits & BIT(irq_bit)); -bail: - spin_unlock(&chip->pm_irq_lock); - - return rc; -} - -static struct irq_chip pm8xxx_irq_chip = { - .name = "pm8xxx", - .irq_mask_ack = pm8xxx_irq_mask_ack, - .irq_unmask = pm8xxx_irq_unmask, - .irq_set_type = pm8xxx_irq_set_type, - .irq_get_irqchip_state = pm8xxx_irq_get_irqchip_state, - .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE, -}; - -static int pm8xxx_irq_domain_map(struct irq_domain *d, unsigned int irq, - irq_hw_number_t hwirq) -{ - struct pm_irq_chip *chip = d->host_data; - - irq_set_chip_and_handler(irq, &pm8xxx_irq_chip, handle_level_irq); - irq_set_chip_data(irq, chip); - irq_set_noprobe(irq); - - return 0; -} - -static const struct irq_domain_ops pm8xxx_irq_domain_ops = { - .xlate = irq_domain_xlate_twocell, - .map = pm8xxx_irq_domain_map, -}; - -static const struct regmap_config ssbi_regmap_config = { - .reg_bits = 16, - .val_bits = 8, - .max_register = 0x3ff, - .fast_io = true, - .reg_read = ssbi_reg_read, - .reg_write = ssbi_reg_write -}; - -static const struct of_device_id pm8921_id_table[] = { - { .compatible = "qcom,pm8018", }, - { .compatible = "qcom,pm8058", }, - { .compatible = "qcom,pm8921", }, - { } -}; -MODULE_DEVICE_TABLE(of, pm8921_id_table); - -static int pm8921_probe(struct platform_device *pdev) -{ - struct regmap *regmap; - int irq, rc; - unsigned int val; - u32 rev; - struct pm_irq_chip *chip; - unsigned int nirqs = PM8921_NR_IRQS; - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - regmap = devm_regmap_init(&pdev->dev, NULL, pdev->dev.parent, - &ssbi_regmap_config); - if (IS_ERR(regmap)) - return PTR_ERR(regmap); - - /* Read PMIC chip revision */ - rc = regmap_read(regmap, REG_HWREV, &val); - if (rc) { - pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); - return rc; - } - pr_info("PMIC revision 1: %02X\n", val); - rev = val; - - /* Read PMIC chip revision 2 */ - rc = regmap_read(regmap, REG_HWREV_2, &val); - if (rc) { - pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", - REG_HWREV_2, rc); - return rc; - } - pr_info("PMIC revision 2: %02X\n", val); - rev |= val << BITS_PER_BYTE; - - chip = devm_kzalloc(&pdev->dev, sizeof(*chip) + - sizeof(chip->config[0]) * nirqs, - GFP_KERNEL); - if (!chip) - return -ENOMEM; - - platform_set_drvdata(pdev, chip); - chip->regmap = regmap; - chip->num_irqs = nirqs; - chip->num_blocks = DIV_ROUND_UP(chip->num_irqs, 8); - chip->num_masters = DIV_ROUND_UP(chip->num_blocks, 8); - spin_lock_init(&chip->pm_irq_lock); - - chip->irqdomain = irq_domain_add_linear(pdev->dev.of_node, nirqs, - &pm8xxx_irq_domain_ops, - chip); - if (!chip->irqdomain) - return -ENODEV; - - irq_set_chained_handler_and_data(irq, pm8xxx_irq_handler, chip); - irq_set_irq_wake(irq, 1); - - rc = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); - if (rc) { - irq_set_chained_handler_and_data(irq, NULL, NULL); - irq_domain_remove(chip->irqdomain); - } - - return rc; -} - -static int pm8921_remove_child(struct device *dev, void *unused) -{ - platform_device_unregister(to_platform_device(dev)); - return 0; -} - -static int pm8921_remove(struct platform_device *pdev) -{ - int irq = platform_get_irq(pdev, 0); - struct pm_irq_chip *chip = platform_get_drvdata(pdev); - - device_for_each_child(&pdev->dev, NULL, pm8921_remove_child); - irq_set_chained_handler_and_data(irq, NULL, NULL); - irq_domain_remove(chip->irqdomain); - - return 0; -} - -static struct platform_driver pm8921_driver = { - .probe = pm8921_probe, - .remove = pm8921_remove, - .driver = { - .name = "pm8921-core", - .of_match_table = pm8921_id_table, - }, -}; - -static int __init pm8921_init(void) -{ - return platform_driver_register(&pm8921_driver); -} -subsys_initcall(pm8921_init); - -static void __exit pm8921_exit(void) -{ - platform_driver_unregister(&pm8921_driver); -} -module_exit(pm8921_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("PMIC 8921 core driver"); -MODULE_VERSION("1.0"); -MODULE_ALIAS("platform:pm8921-core"); diff --git a/drivers/mfd/qcom-pm8xxx.c b/drivers/mfd/qcom-pm8xxx.c new file mode 100644 index 000000000000..7f9620ec61e8 --- /dev/null +++ b/drivers/mfd/qcom-pm8xxx.c @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSBI_REG_ADDR_IRQ_BASE 0x1BB + +#define SSBI_REG_ADDR_IRQ_ROOT (SSBI_REG_ADDR_IRQ_BASE + 0) +#define SSBI_REG_ADDR_IRQ_M_STATUS1 (SSBI_REG_ADDR_IRQ_BASE + 1) +#define SSBI_REG_ADDR_IRQ_M_STATUS2 (SSBI_REG_ADDR_IRQ_BASE + 2) +#define SSBI_REG_ADDR_IRQ_M_STATUS3 (SSBI_REG_ADDR_IRQ_BASE + 3) +#define SSBI_REG_ADDR_IRQ_M_STATUS4 (SSBI_REG_ADDR_IRQ_BASE + 4) +#define SSBI_REG_ADDR_IRQ_BLK_SEL (SSBI_REG_ADDR_IRQ_BASE + 5) +#define SSBI_REG_ADDR_IRQ_IT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 6) +#define SSBI_REG_ADDR_IRQ_CONFIG (SSBI_REG_ADDR_IRQ_BASE + 7) +#define SSBI_REG_ADDR_IRQ_RT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 8) + +#define PM_IRQF_LVL_SEL 0x01 /* level select */ +#define PM_IRQF_MASK_FE 0x02 /* mask falling edge */ +#define PM_IRQF_MASK_RE 0x04 /* mask rising edge */ +#define PM_IRQF_CLR 0x08 /* clear interrupt */ +#define PM_IRQF_BITS_MASK 0x70 +#define PM_IRQF_BITS_SHIFT 4 +#define PM_IRQF_WRITE 0x80 + +#define PM_IRQF_MASK_ALL (PM_IRQF_MASK_FE | \ + PM_IRQF_MASK_RE) + +#define REG_HWREV 0x002 /* PMIC4 revision */ +#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ + +#define PM8XXX_NR_IRQS 256 + +struct pm_irq_chip { + struct regmap *regmap; + spinlock_t pm_irq_lock; + struct irq_domain *irqdomain; + unsigned int num_irqs; + unsigned int num_blocks; + unsigned int num_masters; + u8 config[0]; +}; + +static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, unsigned int bp, + unsigned int *ip) +{ + int rc; + + spin_lock(&chip->pm_irq_lock); + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); + goto bail; + } + + rc = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_IT_STATUS, ip); + if (rc) + pr_err("Failed Reading Status rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + return rc; +} + +static int +pm8xxx_config_irq(struct pm_irq_chip *chip, unsigned int bp, unsigned int cp) +{ + int rc; + + spin_lock(&chip->pm_irq_lock); + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, bp); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", bp, rc); + goto bail; + } + + cp |= PM_IRQF_WRITE; + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_CONFIG, cp); + if (rc) + pr_err("Failed Configuring IRQ rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + return rc; +} + +static int pm8xxx_irq_block_handler(struct pm_irq_chip *chip, int block) +{ + int pmirq, irq, i, ret = 0; + unsigned int bits; + + ret = pm8xxx_read_block_irq(chip, block, &bits); + if (ret) { + pr_err("Failed reading %d block ret=%d", block, ret); + return ret; + } + if (!bits) { + pr_err("block bit set in master but no irqs: %d", block); + return 0; + } + + /* Check IRQ bits */ + for (i = 0; i < 8; i++) { + if (bits & (1 << i)) { + pmirq = block * 8 + i; + irq = irq_find_mapping(chip->irqdomain, pmirq); + generic_handle_irq(irq); + } + } + return 0; +} + +static int pm8xxx_irq_master_handler(struct pm_irq_chip *chip, int master) +{ + unsigned int blockbits; + int block_number, i, ret = 0; + + ret = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_M_STATUS1 + master, + &blockbits); + if (ret) { + pr_err("Failed to read master %d ret=%d\n", master, ret); + return ret; + } + if (!blockbits) { + pr_err("master bit set in root but no blocks: %d", master); + return 0; + } + + for (i = 0; i < 8; i++) + if (blockbits & (1 << i)) { + block_number = master * 8 + i; /* block # */ + ret |= pm8xxx_irq_block_handler(chip, block_number); + } + return ret; +} + +static void pm8xxx_irq_handler(struct irq_desc *desc) +{ + struct pm_irq_chip *chip = irq_desc_get_handler_data(desc); + struct irq_chip *irq_chip = irq_desc_get_chip(desc); + unsigned int root; + int i, ret, masters = 0; + + chained_irq_enter(irq_chip, desc); + + ret = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_ROOT, &root); + if (ret) { + pr_err("Can't read root status ret=%d\n", ret); + return; + } + + /* on pm8xxx series masters start from bit 1 of the root */ + masters = root >> 1; + + /* Read allowed masters for blocks. */ + for (i = 0; i < chip->num_masters; i++) + if (masters & (1 << i)) + pm8xxx_irq_master_handler(chip, i); + + chained_irq_exit(irq_chip, desc); +} + +static void pm8xxx_irq_mask_ack(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + u8 block, config; + + block = pmirq / 8; + + config = chip->config[pmirq] | PM_IRQF_MASK_ALL | PM_IRQF_CLR; + pm8xxx_config_irq(chip, block, config); +} + +static void pm8xxx_irq_unmask(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + u8 block, config; + + block = pmirq / 8; + + config = chip->config[pmirq]; + pm8xxx_config_irq(chip, block, config); +} + +static int pm8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + int irq_bit; + u8 block, config; + + block = pmirq / 8; + irq_bit = pmirq % 8; + + chip->config[pmirq] = (irq_bit << PM_IRQF_BITS_SHIFT) + | PM_IRQF_MASK_ALL; + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + if (flow_type & IRQF_TRIGGER_RISING) + chip->config[pmirq] &= ~PM_IRQF_MASK_RE; + if (flow_type & IRQF_TRIGGER_FALLING) + chip->config[pmirq] &= ~PM_IRQF_MASK_FE; + } else { + chip->config[pmirq] |= PM_IRQF_LVL_SEL; + + if (flow_type & IRQF_TRIGGER_HIGH) + chip->config[pmirq] &= ~PM_IRQF_MASK_RE; + else + chip->config[pmirq] &= ~PM_IRQF_MASK_FE; + } + + config = chip->config[pmirq] | PM_IRQF_CLR; + return pm8xxx_config_irq(chip, block, config); +} + +static int pm8xxx_irq_get_irqchip_state(struct irq_data *d, + enum irqchip_irq_state which, + bool *state) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + unsigned int bits; + int irq_bit; + u8 block; + int rc; + + if (which != IRQCHIP_STATE_LINE_LEVEL) + return -EINVAL; + + block = pmirq / 8; + irq_bit = pmirq % 8; + + spin_lock(&chip->pm_irq_lock); + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, block); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", block, rc); + goto bail; + } + + rc = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits); + if (rc) { + pr_err("Failed Reading Status rc=%d\n", rc); + goto bail; + } + + *state = !!(bits & BIT(irq_bit)); +bail: + spin_unlock(&chip->pm_irq_lock); + + return rc; +} + +static struct irq_chip pm8xxx_irq_chip = { + .name = "pm8xxx", + .irq_mask_ack = pm8xxx_irq_mask_ack, + .irq_unmask = pm8xxx_irq_unmask, + .irq_set_type = pm8xxx_irq_set_type, + .irq_get_irqchip_state = pm8xxx_irq_get_irqchip_state, + .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE, +}; + +static int pm8xxx_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct pm_irq_chip *chip = d->host_data; + + irq_set_chip_and_handler(irq, &pm8xxx_irq_chip, handle_level_irq); + irq_set_chip_data(irq, chip); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops pm8xxx_irq_domain_ops = { + .xlate = irq_domain_xlate_twocell, + .map = pm8xxx_irq_domain_map, +}; + +static const struct regmap_config ssbi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0x3ff, + .fast_io = true, + .reg_read = ssbi_reg_read, + .reg_write = ssbi_reg_write +}; + +static const struct of_device_id pm8xxx_id_table[] = { + { .compatible = "qcom,pm8018", }, + { .compatible = "qcom,pm8058", }, + { .compatible = "qcom,pm8921", }, + { } +}; +MODULE_DEVICE_TABLE(of, pm8xxx_id_table); + +static int pm8xxx_probe(struct platform_device *pdev) +{ + struct regmap *regmap; + int irq, rc; + unsigned int val; + u32 rev; + struct pm_irq_chip *chip; + unsigned int nirqs = PM8XXX_NR_IRQS; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + regmap = devm_regmap_init(&pdev->dev, NULL, pdev->dev.parent, + &ssbi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Read PMIC chip revision */ + rc = regmap_read(regmap, REG_HWREV, &val); + if (rc) { + pr_err("Failed to read hw rev reg %d:rc=%d\n", REG_HWREV, rc); + return rc; + } + pr_info("PMIC revision 1: %02X\n", val); + rev = val; + + /* Read PMIC chip revision 2 */ + rc = regmap_read(regmap, REG_HWREV_2, &val); + if (rc) { + pr_err("Failed to read hw rev 2 reg %d:rc=%d\n", + REG_HWREV_2, rc); + return rc; + } + pr_info("PMIC revision 2: %02X\n", val); + rev |= val << BITS_PER_BYTE; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip) + + sizeof(chip->config[0]) * nirqs, + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + platform_set_drvdata(pdev, chip); + chip->regmap = regmap; + chip->num_irqs = nirqs; + chip->num_blocks = DIV_ROUND_UP(chip->num_irqs, 8); + chip->num_masters = DIV_ROUND_UP(chip->num_blocks, 8); + spin_lock_init(&chip->pm_irq_lock); + + chip->irqdomain = irq_domain_add_linear(pdev->dev.of_node, nirqs, + &pm8xxx_irq_domain_ops, + chip); + if (!chip->irqdomain) + return -ENODEV; + + irq_set_chained_handler_and_data(irq, pm8xxx_irq_handler, chip); + irq_set_irq_wake(irq, 1); + + rc = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + if (rc) { + irq_set_chained_handler_and_data(irq, NULL, NULL); + irq_domain_remove(chip->irqdomain); + } + + return rc; +} + +static int pm8xxx_remove_child(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +static int pm8xxx_remove(struct platform_device *pdev) +{ + int irq = platform_get_irq(pdev, 0); + struct pm_irq_chip *chip = platform_get_drvdata(pdev); + + device_for_each_child(&pdev->dev, NULL, pm8xxx_remove_child); + irq_set_chained_handler_and_data(irq, NULL, NULL); + irq_domain_remove(chip->irqdomain); + + return 0; +} + +static struct platform_driver pm8xxx_driver = { + .probe = pm8xxx_probe, + .remove = pm8xxx_remove, + .driver = { + .name = "pm8xxx-core", + .of_match_table = pm8xxx_id_table, + }, +}; + +static int __init pm8xxx_init(void) +{ + return platform_driver_register(&pm8xxx_driver); +} +subsys_initcall(pm8xxx_init); + +static void __exit pm8xxx_exit(void) +{ + platform_driver_unregister(&pm8xxx_driver); +} +module_exit(pm8xxx_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC 8xxx core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8xxx-core"); -- cgit v1.2.3