From patchwork Tue Oct 20 22:33:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guru Das Srinagesh X-Patchwork-Id: 11848003 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 867A61580 for ; Tue, 20 Oct 2020 22:34:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6138A223BF for ; Tue, 20 Oct 2020 22:34:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2439102AbgJTWd4 (ORCPT ); Tue, 20 Oct 2020 18:33:56 -0400 Received: from alexa-out-sd-01.qualcomm.com ([199.106.114.38]:55334 "EHLO alexa-out-sd-01.qualcomm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2439095AbgJTWdz (ORCPT ); Tue, 20 Oct 2020 18:33:55 -0400 Received: from unknown (HELO ironmsg02-sd.qualcomm.com) ([10.53.140.142]) by alexa-out-sd-01.qualcomm.com with ESMTP; 20 Oct 2020 15:33:55 -0700 X-QCInternal: smtphost Received: from gurus-linux.qualcomm.com ([10.46.162.81]) by ironmsg02-sd.qualcomm.com with ESMTP; 20 Oct 2020 15:33:53 -0700 Received: by gurus-linux.qualcomm.com (Postfix, from userid 383780) id A00FA176C; Tue, 20 Oct 2020 15:33:53 -0700 (PDT) From: Guru Das Srinagesh To: Mark Brown , Markus Elfring , Lee Jones , Rob Herring Cc: Bjorn Andersson , Greg KH , Guenter Roeck , Joe Perches , Subbaraman Narayanamurthy , David Collins , Anirudh Ghayal , linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Guru Das Srinagesh Subject: [RFC PATCH RESEND v1 1/3] regmap-irq: Add support for peripheral offsets Date: Tue, 20 Oct 2020 15:33:41 -0700 Message-Id: <40581a58bd16442f03db1abea014ca1eedc94d3c.1603232320.git.gurus@codeaurora.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: References: In-Reply-To: References: Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org Some MFD chips do not have the register space for their peripherals mapped out with a fixed stride. Add peripheral address offsets to the framework to support such address spaces. In this new scheme, the regmap-irq client registering with the framework shall have to define the *_base registers (e.g. status_base, mask_base, type_base, etc.) as those of the very first peripheral in the chip, and then specify address offsets of each subsequent peripheral so that their corresponding *_base registers may be calculated by the framework. The first element of the periph_offs array must be zero so that the first peripherals' addresses may be accessed. Some MFD chips define two registers in addition to the IRQ type registers: POLARITY_HI and POLARITY_LO, so add support to manage their data as well as write to them. Signed-off-by: Guru Das Srinagesh --- drivers/base/regmap/regmap-irq.c | 191 ++++++++++++++++++++++++++++++++------- include/linux/regmap.h | 6 ++ 2 files changed, 163 insertions(+), 34 deletions(-) diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index ad5c2de..dbf2c86 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -38,6 +38,8 @@ struct regmap_irq_chip_data { unsigned int *wake_buf; unsigned int *type_buf; unsigned int *type_buf_def; + unsigned int *polarity_hi_buf; + unsigned int *polarity_lo_buf; unsigned int irq_reg_stride; unsigned int type_reg_stride; @@ -87,8 +89,13 @@ static void regmap_irq_sync_unlock(struct irq_data *data) if (d->clear_status) { for (i = 0; i < d->chip->num_regs; i++) { - reg = d->chip->status_base + - (i * map->reg_stride * d->irq_reg_stride); + if (d->chip->periph_offs) + reg = d->chip->status_base + + d->chip->periph_offs[i]; + else + reg = d->chip->status_base + + (i * map->reg_stride * + d->irq_reg_stride); ret = regmap_read(map, reg, &val); if (ret) @@ -108,8 +115,13 @@ static void regmap_irq_sync_unlock(struct irq_data *data) if (!d->chip->mask_base) continue; - reg = d->chip->mask_base + - (i * map->reg_stride * d->irq_reg_stride); + if (d->chip->periph_offs) + reg = d->chip->mask_base + + d->chip->periph_offs[i]; + else + reg = d->chip->mask_base + + (i * map->reg_stride * d->irq_reg_stride); + if (d->chip->mask_invert) { ret = regmap_irq_update_bits(d, reg, d->mask_buf_def[i], ~d->mask_buf[i]); @@ -136,8 +148,13 @@ static void regmap_irq_sync_unlock(struct irq_data *data) dev_err(d->map->dev, "Failed to sync masks in %x\n", reg); - reg = d->chip->wake_base + - (i * map->reg_stride * d->irq_reg_stride); + if (d->chip->periph_offs) + reg = d->chip->wake_base + + d->chip->periph_offs[i]; + else + reg = d->chip->wake_base + + (i * map->reg_stride * d->irq_reg_stride); + if (d->wake_buf) { if (d->chip->wake_invert) ret = regmap_irq_update_bits(d, reg, @@ -161,8 +178,14 @@ static void regmap_irq_sync_unlock(struct irq_data *data) * it'll be ignored in irq handler, then may introduce irq storm */ if (d->mask_buf[i] && (d->chip->ack_base || d->chip->use_ack)) { - reg = d->chip->ack_base + - (i * map->reg_stride * d->irq_reg_stride); + if (d->chip->periph_offs) + reg = d->chip->ack_base + + d->chip->periph_offs[i]; + else + reg = d->chip->ack_base + + (i * map->reg_stride * + d->irq_reg_stride); + /* some chips ack by write 0 */ if (d->chip->ack_invert) ret = regmap_write(map, reg, ~d->mask_buf[i]); @@ -187,8 +210,14 @@ static void regmap_irq_sync_unlock(struct irq_data *data) for (i = 0; i < d->chip->num_type_reg; i++) { if (!d->type_buf_def[i]) continue; - reg = d->chip->type_base + - (i * map->reg_stride * d->type_reg_stride); + if (d->chip->periph_offs) + reg = d->chip->type_base + + d->chip->periph_offs[i]; + else + reg = d->chip->type_base + + (i * map->reg_stride * + d->type_reg_stride); + if (d->chip->type_invert) ret = regmap_irq_update_bits(d, reg, d->type_buf_def[i], ~d->type_buf[i]); @@ -198,6 +227,25 @@ static void regmap_irq_sync_unlock(struct irq_data *data) if (ret != 0) dev_err(d->map->dev, "Failed to sync type in %x\n", reg); + + if (!d->chip->periph_offs || + !d->chip->polarity_hi_base || + !d->chip->polarity_lo_base) + continue; + + reg = d->chip->polarity_hi_base + + d->chip->periph_offs[i]; + ret = regmap_write(map, reg, d->polarity_hi_buf[i]); + if (ret != 0) + dev_err(d->map->dev, "Failed to sync polarity hi in %x\n", + reg); + + reg = d->chip->polarity_lo_base + + d->chip->periph_offs[i]; + ret = regmap_write(map, reg, d->polarity_lo_buf[i]); + if (ret != 0) + dev_err(d->map->dev, "Failed to sync polarity lo in %x\n", + reg); } } @@ -280,23 +328,49 @@ static int regmap_irq_set_type(struct irq_data *data, unsigned int type) switch (type) { case IRQ_TYPE_EDGE_FALLING: d->type_buf[reg] |= t->type_falling_val; + if (d->chip->periph_offs) { + d->polarity_hi_buf[reg] &= ~t->type_falling_val; + d->polarity_lo_buf[reg] |= t->type_falling_val; + } break; case IRQ_TYPE_EDGE_RISING: d->type_buf[reg] |= t->type_rising_val; + if (d->chip->periph_offs) { + d->polarity_hi_buf[reg] |= t->type_rising_val; + d->polarity_lo_buf[reg] &= ~t->type_rising_val; + } break; case IRQ_TYPE_EDGE_BOTH: d->type_buf[reg] |= (t->type_falling_val | t->type_rising_val); + if (d->chip->periph_offs) { + d->polarity_hi_buf[reg] |= (t->type_falling_val | + t->type_rising_val); + d->polarity_lo_buf[reg] |= (t->type_falling_val | + t->type_rising_val); + } break; case IRQ_TYPE_LEVEL_HIGH: - d->type_buf[reg] |= t->type_level_high_val; + if (!d->chip->periph_offs) { + d->type_buf[reg] |= t->type_level_high_val; + } else { + d->type_buf[reg] &= ~t->type_level_high_val; + d->polarity_hi_buf[reg] |= t->type_level_high_val; + d->polarity_lo_buf[reg] &= ~t->type_level_high_val; + } break; case IRQ_TYPE_LEVEL_LOW: - d->type_buf[reg] |= t->type_level_low_val; + if (!d->chip->periph_offs) { + d->type_buf[reg] |= t->type_level_low_val; + } else { + d->type_buf[reg] &= ~t->type_level_low_val; + d->polarity_hi_buf[reg] &= ~t->type_level_low_val; + d->polarity_lo_buf[reg] |= t->type_level_low_val; + } break; default: return -EINVAL; @@ -342,12 +416,10 @@ static inline int read_sub_irq_data(struct regmap_irq_chip_data *data, struct regmap_irq_sub_irq_map *subreg; int i, ret = 0; - if (!chip->sub_reg_offsets) { - /* Assume linear mapping */ - ret = regmap_read(map, chip->status_base + - (b * map->reg_stride * data->irq_reg_stride), - &data->status_buf[b]); - } else { + if (chip->periph_offs) { + ret = regmap_read(map, chip->status_base + chip->periph_offs[b], + &data->status_buf[b]); + } else if (chip->sub_reg_offsets) { subreg = &chip->sub_reg_offsets[b]; for (i = 0; i < subreg->num_regs; i++) { unsigned int offset = subreg->offset[i]; @@ -357,6 +429,11 @@ static inline int read_sub_irq_data(struct regmap_irq_chip_data *data, if (ret) break; } + } else { + /* Assume linear mapping */ + ret = regmap_read(map, chip->status_base + + (b * map->reg_stride * data->irq_reg_stride), + &data->status_buf[b]); } return ret; } @@ -474,10 +551,14 @@ static irqreturn_t regmap_irq_thread(int irq, void *d) } else { for (i = 0; i < data->chip->num_regs; i++) { - ret = regmap_read(map, chip->status_base + - (i * map->reg_stride - * data->irq_reg_stride), - &data->status_buf[i]); + if (chip->periph_offs) + reg = chip->status_base + chip->periph_offs[i]; + else + reg = chip->status_base + + (i * map->reg_stride * + data->irq_reg_stride); + + ret = regmap_read(map, reg, &data->status_buf[i]); if (ret != 0) { dev_err(map->dev, @@ -499,8 +580,13 @@ static irqreturn_t regmap_irq_thread(int irq, void *d) data->status_buf[i] &= ~data->mask_buf[i]; if (data->status_buf[i] && (chip->ack_base || chip->use_ack)) { - reg = chip->ack_base + - (i * map->reg_stride * data->irq_reg_stride); + if (chip->periph_offs) + reg = chip->ack_base + chip->periph_offs[i]; + else + reg = chip->ack_base + + (i * map->reg_stride * + data->irq_reg_stride); + if (chip->ack_invert) ret = regmap_write(map, reg, ~data->status_buf[i]); @@ -662,6 +748,18 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, goto err_alloc; } + if (chip->periph_offs) { + d->polarity_hi_buf = kcalloc(chip->num_regs, + sizeof(unsigned int), GFP_KERNEL); + if (!d->polarity_hi_buf) + goto err_alloc; + + d->polarity_lo_buf = kcalloc(chip->num_regs, + sizeof(unsigned int), GFP_KERNEL); + if (!d->polarity_lo_buf) + goto err_alloc; + } + d->irq_chip = regmap_irq_chip; d->irq_chip.name = chip->name; d->irq = irq; @@ -700,8 +798,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, if (!chip->mask_base) continue; - reg = chip->mask_base + - (i * map->reg_stride * d->irq_reg_stride); + if (chip->periph_offs) + reg = chip->mask_base + chip->periph_offs[i]; + else + reg = chip->mask_base + + (i * map->reg_stride * d->irq_reg_stride); + if (chip->mask_invert) ret = regmap_irq_update_bits(d, reg, d->mask_buf[i], ~d->mask_buf[i]); @@ -725,8 +827,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, continue; /* Ack masked but set interrupts */ - reg = chip->status_base + - (i * map->reg_stride * d->irq_reg_stride); + if (chip->periph_offs) + reg = chip->status_base + chip->periph_offs[i]; + else + reg = chip->status_base + + (i * map->reg_stride * d->irq_reg_stride); + ret = regmap_read(map, reg, &d->status_buf[i]); if (ret != 0) { dev_err(map->dev, "Failed to read IRQ status: %d\n", @@ -735,8 +841,13 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, } if (d->status_buf[i] && (chip->ack_base || chip->use_ack)) { - reg = chip->ack_base + - (i * map->reg_stride * d->irq_reg_stride); + if (chip->periph_offs) + reg = chip->ack_base + chip->periph_offs[i]; + else + reg = chip->ack_base + + (i * map->reg_stride * + d->irq_reg_stride); + if (chip->ack_invert) ret = regmap_write(map, reg, ~(d->status_buf[i] & d->mask_buf[i])); @@ -765,8 +876,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, if (d->wake_buf) { for (i = 0; i < chip->num_regs; i++) { d->wake_buf[i] = d->mask_buf_def[i]; - reg = chip->wake_base + - (i * map->reg_stride * d->irq_reg_stride); + if (chip->periph_offs) + reg = chip->wake_base + chip->periph_offs[i]; + else + reg = chip->wake_base + + (i * map->reg_stride * + d->irq_reg_stride); if (chip->wake_invert) ret = regmap_irq_update_bits(d, reg, @@ -786,8 +901,12 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, if (chip->num_type_reg && !chip->type_in_mask) { for (i = 0; i < chip->num_type_reg; ++i) { - reg = chip->type_base + - (i * map->reg_stride * d->type_reg_stride); + if (chip->periph_offs) + reg = chip->type_base + chip->periph_offs[i]; + else + reg = chip->type_base + + (i * map->reg_stride * + d->type_reg_stride); ret = regmap_read(map, reg, &d->type_buf_def[i]); @@ -833,6 +952,8 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, /* Should really dispose of the domain but... */ err_alloc: kfree(d->type_buf); + kfree(d->polarity_hi_buf); + kfree(d->polarity_lo_buf); kfree(d->type_buf_def); kfree(d->wake_buf); kfree(d->mask_buf_def); @@ -903,6 +1024,8 @@ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d) irq_domain_remove(d->domain); kfree(d->type_buf); + kfree(d->polarity_hi_buf); + kfree(d->polarity_lo_buf); kfree(d->type_buf_def); kfree(d->wake_buf); kfree(d->mask_buf_def); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index e7834d9..6fb1090 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -1338,6 +1338,7 @@ struct regmap_irq_sub_irq_map { * status_base. Should contain num_regs arrays. * Can be provided for chips with more complex mapping than * 1.st bit to 1.st sub-reg, 2.nd bit to 2.nd sub-reg, ... + * @periph_offs: Array of addresses of peripherals, should be num_reg long. * @num_main_regs: Number of 'main status' irq registers for chips which have * main_status set. * @@ -1350,6 +1351,8 @@ struct regmap_irq_sub_irq_map { * Using zero value is possible with @use_ack bit. * @wake_base: Base address for wake enables. If zero unsupported. * @type_base: Base address for irq type. If zero unsupported. + * @polarity_hi_base: Base address for polarity high when periph_offs is used. + * @polarity_lo_base: Base address for polarity low when periph_offs is used. * @irq_reg_stride: Stride to use for chips where registers are not contiguous. * @init_ack_masked: Ack all masked interrupts once during initalization. * @mask_invert: Inverted mask register: cleared bits are masked out. @@ -1390,6 +1393,7 @@ struct regmap_irq_chip { unsigned int main_status; unsigned int num_main_status_bits; struct regmap_irq_sub_irq_map *sub_reg_offsets; + unsigned int *periph_offs; int num_main_regs; unsigned int status_base; @@ -1398,6 +1402,8 @@ struct regmap_irq_chip { unsigned int ack_base; unsigned int wake_base; unsigned int type_base; + unsigned int polarity_hi_base; + unsigned int polarity_lo_base; unsigned int irq_reg_stride; bool mask_writeonly:1; bool init_ack_masked:1; From patchwork Tue Oct 20 22:33:42 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guru Das Srinagesh X-Patchwork-Id: 11848007 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 17BD41580 for ; Tue, 20 Oct 2020 22:34:12 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 00BEA2237B for ; Tue, 20 Oct 2020 22:34:11 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2439112AbgJTWeB (ORCPT ); Tue, 20 Oct 2020 18:34:01 -0400 Received: from alexa-out-sd-02.qualcomm.com ([199.106.114.39]:59963 "EHLO alexa-out-sd-02.qualcomm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2439083AbgJTWdz (ORCPT ); Tue, 20 Oct 2020 18:33:55 -0400 Received: from unknown (HELO ironmsg-SD-alpha.qualcomm.com) ([10.53.140.30]) by alexa-out-sd-02.qualcomm.com with ESMTP; 20 Oct 2020 15:33:53 -0700 X-QCInternal: smtphost Received: from gurus-linux.qualcomm.com ([10.46.162.81]) by ironmsg-SD-alpha.qualcomm.com with ESMTP; 20 Oct 2020 15:33:53 -0700 Received: by gurus-linux.qualcomm.com (Postfix, from userid 383780) id A6546194C; Tue, 20 Oct 2020 15:33:53 -0700 (PDT) From: Guru Das Srinagesh To: Mark Brown , Markus Elfring , Lee Jones , Rob Herring Cc: Bjorn Andersson , Greg KH , Guenter Roeck , Joe Perches , Subbaraman Narayanamurthy , David Collins , Anirudh Ghayal , linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Guru Das Srinagesh Subject: [RFC PATCH RESEND v1 2/3] dt-bindings: mfd: Add QCOM PM8008 MFD bindings Date: Tue, 20 Oct 2020 15:33:42 -0700 Message-Id: <7a89811f36fe858756daa62f1162d18da7e79a73.1603232320.git.gurus@codeaurora.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: References: In-Reply-To: References: Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org Add device tree bindings for the driver for Qualcomm Technology Inc.'s PM8008 MFD PMIC. Signed-off-by: Guru Das Srinagesh --- .../bindings/mfd/qcom,pm8008-irqchip.yaml | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/qcom,pm8008-irqchip.yaml diff --git a/Documentation/devicetree/bindings/mfd/qcom,pm8008-irqchip.yaml b/Documentation/devicetree/bindings/mfd/qcom,pm8008-irqchip.yaml new file mode 100644 index 0000000..296ff85 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/qcom,pm8008-irqchip.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/qcom/qcom,pm8008-irqchip.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. PM8008 Multi-Function Device PMIC + +maintainers: + - Guru Das Srinagesh + +description: | + PM8008 is a PMIC that contains 7 LDOs, 2 GPIOs, temperature monitoring, and + can be interfaced over I2C. + +properties: + compatible: + const: qcom,pm8008-irqchip + + reg: + maxItems: 1 + + interrupt-names: + const: pm8008 + + interrupts: + maxItems: 1 + + interrupt-controller: + type: boolean + + "#address-cells": + const: 1 + description: + Must be specified if child nodes are specified. + + "#size-cells": + const: 0 + description: + Must be specified if child nodes are specified. + + "#interrupt-cells": + const: 2 + description: + The first cell is the IRQ number, the second cell is the IRQ trigger flag. + +patternProperties: + "^.*[a-zA-Z][a-zA-Z0-9,+\\-._]{0,63}@[0-9a-f]+$": + type: object + # Each peripheral in PM8008 must be represented as a child node with an + # optional label for referencing as phandle elsewhere. This is optional. + properties: + compatible: + description: + The compatible string for the peripheral's driver. + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + required: + - compatible + - reg + - interrupts + +required: + - compatible + - reg + - interrupts + - "#interrupt-cells" + +additionalProperties: false + +examples: + - | + &qupv3_se13_i2c { + #address-cells = <1>; + #size-cells = <0>; + + pm8008i@8 { + compatible = "qcom,pm8008-irqchip"; + reg = <0x8>; + #address-cells = <1>; + #size-cells = <0>; + interrupt-controller; + #interrupt-cells = <2>; + + interrupt-names = "pm8008"; + interrupt-parent = <&tlmm>; + interrupts = <32 IRQ_TYPE_EDGE_RISING>; + + pm8008_tz: qcom,temp-alarm@2400 { + compatible = "qcom,spmi-temp-alarm"; + reg = <0x2400>; + interrupts = <0x5 IRQ_TYPE_EDGE_BOTH>; + #thermal-sensor-cells = <0>; + }; + }; + }; + +... From patchwork Tue Oct 20 22:33:43 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guru Das Srinagesh X-Patchwork-Id: 11848005 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CCE0614B2 for ; Tue, 20 Oct 2020 22:34:02 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B89B92242F for ; Tue, 20 Oct 2020 22:34:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2439116AbgJTWeC (ORCPT ); Tue, 20 Oct 2020 18:34:02 -0400 Received: from alexa-out-sd-02.qualcomm.com ([199.106.114.39]:59963 "EHLO alexa-out-sd-02.qualcomm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2439100AbgJTWd4 (ORCPT ); Tue, 20 Oct 2020 18:33:56 -0400 Received: from unknown (HELO ironmsg-SD-alpha.qualcomm.com) ([10.53.140.30]) by alexa-out-sd-02.qualcomm.com with ESMTP; 20 Oct 2020 15:33:53 -0700 X-QCInternal: smtphost Received: from gurus-linux.qualcomm.com ([10.46.162.81]) by ironmsg-SD-alpha.qualcomm.com with ESMTP; 20 Oct 2020 15:33:53 -0700 Received: by gurus-linux.qualcomm.com (Postfix, from userid 383780) id C4F26194C; Tue, 20 Oct 2020 15:33:53 -0700 (PDT) From: Guru Das Srinagesh To: Mark Brown , Markus Elfring , Lee Jones , Rob Herring Cc: Bjorn Andersson , Greg KH , Guenter Roeck , Joe Perches , Subbaraman Narayanamurthy , David Collins , Anirudh Ghayal , linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Guru Das Srinagesh Subject: [RFC PATCH RESEND v1 3/3] mfd: Add PM8008 driver Date: Tue, 20 Oct 2020 15:33:43 -0700 Message-Id: X-Mailer: git-send-email 2.7.4 In-Reply-To: References: In-Reply-To: References: Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org PM8008 is a PMIC that contains 7 LDOs, 2 GPIOs, temperature monitoring, and can be interfaced over I2C. This driver uses the regmap-irq framework to handle interrupts, creates a regmap and uses it to instantiate all the child nodes under it in the device tree. Only four peripherals have been added at the moment. Every peripheral that has its TYPE register's hw default value as zero must have it set to an all-ones mask (to cover all interrupts it supports) in pm8008_init(). This is as per commit 84267d1b18ab ("regmap: regmap-irq: Remove default irq type setting from core"). Signed-off-by: Guru Das Srinagesh --- drivers/mfd/Kconfig | 14 ++++ drivers/mfd/Makefile | 1 + drivers/mfd/qcom-pm8008.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 drivers/mfd/qcom-pm8008.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 33df083..3d4e989 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1960,6 +1960,20 @@ config MFD_ROHM_BD70528 10 bits SAR ADC for battery temperature monitor and 1S battery charger. +config MFD_QCOM_PM8008 + tristate "QCOM PM8008 Power Management IC" + depends on I2C && OF + select REGMAP_I2C + select REGMAP_IRQ + help + Select this option to get support for the PM8008 PMIC chip. PM8008 is + a low-cost PMIC that contains 7 LDOs, 2 GPIOs, temperature + monitoring, and can be interfaced over I2C. This driver provides + common support for accessing the device by instantiating all the + child nodes under it in the device tree and, therefore, additional + drivers must be enabled in order to use the functionality of the + device. + config MFD_ROHM_BD71828 tristate "ROHM BD71828 Power Management IC" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index a60e5f8..e316064 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -260,6 +260,7 @@ obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o obj-$(CONFIG_MFD_ROHM_BD70528) += rohm-bd70528.o obj-$(CONFIG_MFD_ROHM_BD71828) += rohm-bd71828.o obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o +obj-$(CONFIG_MFD_QCOM_PM8008) += qcom-pm8008.o obj-$(CONFIG_MFD_STMFX) += stmfx.o obj-$(CONFIG_MFD_KHADAS_MCU) += khadas-mcu.o diff --git a/drivers/mfd/qcom-pm8008.c b/drivers/mfd/qcom-pm8008.c new file mode 100644 index 0000000..56a5951 --- /dev/null +++ b/drivers/mfd/qcom-pm8008.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#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 PM8008_NUM_PERIPHS 4 + +#define PM8008_PERIPH_0_BASE 0x900 +#define PM8008_PERIPH_1_BASE 0x2400 +#define PM8008_PERIPH_2_BASE 0xC000 +#define PM8008_PERIPH_3_BASE 0xC100 + +#define PM8008_TEMP_ALARM_ADDR PM8008_PERIPH_1_BASE +#define PM8008_TEMP_ALARM_EN 0x1 + +#define PM8008_STATUS_BASE (PM8008_PERIPH_0_BASE | INT_LATCHED_STS_OFFSET) +#define PM8008_MASK_BASE (PM8008_PERIPH_0_BASE | INT_EN_SET_OFFSET) +#define PM8008_UNMASK_BASE (PM8008_PERIPH_0_BASE | INT_EN_CLR_OFFSET) +#define PM8008_TYPE_BASE (PM8008_PERIPH_0_BASE | INT_SET_TYPE_OFFSET) +#define PM8008_ACK_BASE (PM8008_PERIPH_0_BASE | INT_LATCHED_CLR_OFFSET) +#define PM8008_POLARITY_HI_BASE (PM8008_PERIPH_0_BASE | INT_POL_HIGH_OFFSET) +#define PM8008_POLARITY_LO_BASE (PM8008_PERIPH_0_BASE | INT_POL_LOW_OFFSET) + +#define ADDRESS_OFFSET(paddr, base) (paddr - base) + +#define PM8008_PERIPH_OFFSET(paddr) \ + ADDRESS_OFFSET(paddr, PM8008_PERIPH_0_BASE) + +struct pm8008_data { + struct device *dev; + struct regmap *regmap; + int irq; + struct regmap_irq_chip_data *irq_data; +}; + +unsigned int pm8008_periph_offs[] = { + PM8008_PERIPH_OFFSET(PM8008_PERIPH_0_BASE), + PM8008_PERIPH_OFFSET(PM8008_PERIPH_1_BASE), + PM8008_PERIPH_OFFSET(PM8008_PERIPH_2_BASE), + PM8008_PERIPH_OFFSET(PM8008_PERIPH_3_BASE), +}; + +/* Need to define enums for the interrupt numbers and masks */ +static struct regmap_irq pm8008_irqs[] = { + /* MISC IRQs */ + REGMAP_IRQ_REG(0, 0, BIT(0)), + REGMAP_IRQ_REG(1, 0, BIT(1)), + REGMAP_IRQ_REG(2, 0, BIT(2)), + REGMAP_IRQ_REG(3, 0, BIT(3)), + REGMAP_IRQ_REG(4, 0, BIT(4)), + /* TEMP ALARM IRQs */ + REGMAP_IRQ_REG(5, 1, BIT(0)), + /* GPIO1 IRQs */ + REGMAP_IRQ_REG(6, 2, BIT(0)), + /* GPIO2 IRQs */ + REGMAP_IRQ_REG(7, 3, BIT(0)), +}; + +static struct regmap_irq_chip pm8008_irq_chip = { + .name = "pm8008_irq", + .main_status = I2C_INTR_STATUS_BASE, + .num_main_regs = 1, + .irqs = pm8008_irqs, + .num_irqs = ARRAY_SIZE(pm8008_irqs), + .num_regs = PM8008_NUM_PERIPHS, + .periph_offs = pm8008_periph_offs, + .status_base = PM8008_STATUS_BASE, + /* + * mask_base and unmask_base are swapped (SET and CLR) because + * "unmask_offset" in framework expects unmask_base to be larger than + * mask_base. + */ + .mask_base = PM8008_MASK_BASE, + .unmask_base = PM8008_UNMASK_BASE, + .type_base = PM8008_TYPE_BASE, + .ack_base = PM8008_ACK_BASE, + .polarity_hi_base = PM8008_POLARITY_HI_BASE, + .polarity_lo_base = PM8008_POLARITY_LO_BASE, + .num_type_reg = PM8008_NUM_PERIPHS, +}; + +static struct regmap_config i2c_pmic_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xFFFF, +}; + +static int pm8008_init(struct pm8008_data *chip) +{ + /* + * Set TEMP_ALARM peripheral's TYPE so that the regmap-irq framework + * reads this as the default value instead of zero, the HW default. + */ + + return regmap_write(chip->regmap, + (PM8008_TEMP_ALARM_ADDR | INT_SET_TYPE_OFFSET), + PM8008_TEMP_ALARM_EN); +} + +static int pm8008_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc, i; + struct regmap_irq_type *type; + struct pm8008_data *chip; + struct regmap_irq_chip_data *irq_data; + + 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; + + i2c_set_clientdata(client, chip); + + rc = pm8008_init(chip); + if (rc) { + dev_err(chip->dev, "Init failed: %d\n", rc); + return rc; + } + + for (i = 0; i < ARRAY_SIZE(pm8008_irqs); i++) { + type = &pm8008_irqs[i].type; + + /* All IRQs support both edge and level triggers */ + type->types_supported = (IRQ_TYPE_EDGE_BOTH | + IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW); + + type->type_reg_offset = pm8008_irqs[i].reg_offset; + type->type_rising_val = pm8008_irqs[i].mask; + type->type_falling_val = pm8008_irqs[i].mask; + type->type_level_high_val = pm8008_irqs[i].mask; + type->type_level_low_val = pm8008_irqs[i].mask; + } + + rc = devm_regmap_add_irq_chip(chip->dev, chip->regmap, client->irq, + IRQF_SHARED, 0, &pm8008_irq_chip, &irq_data); + if (rc) { + dev_err(chip->dev, "Failed to add IRQ chip: %d\n", rc); + return rc; + } + + return devm_of_platform_populate(chip->dev); +} + +static int pm8008_remove(struct i2c_client *client) +{ + i2c_set_clientdata(client, NULL); + + return 0; +} + +static const struct of_device_id pm8008_match[] = { + { .compatible = "qcom,pm8008-irqchip", }, + { }, +}; + +static const struct i2c_device_id i2c_pmic_id[] = { + { "qcom-i2c-pmic", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, i2c_pmic_id); + +static struct i2c_driver pm8008_irq_driver = { + .driver = { + .name = "pm8008-irqchip", + .of_match_table = pm8008_match, + }, + .probe = pm8008_probe, + .remove = pm8008_remove, + .id_table = i2c_pmic_id, +}; +module_i2c_driver(pm8008_irq_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:qcom-pm8008");