From patchwork Mon Mar 5 02:56:52 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "\(Exiting\) Baolin Wang" X-Patchwork-Id: 130610 Delivered-To: patch@linaro.org Received: by 10.46.66.2 with SMTP id p2csp2203802lja; Sun, 4 Mar 2018 18:58:11 -0800 (PST) X-Google-Smtp-Source: AG47ELt2MdNqyyLKFMOfInDX3BJksrX0ezMyZUWPrjh535eql3f/Q7dO+GfRqqpVUit1zjvwmBsa X-Received: by 10.98.245.131 with SMTP id b3mr13864439pfm.20.1520218691441; Sun, 04 Mar 2018 18:58:11 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1520218691; cv=none; d=google.com; s=arc-20160816; b=L4mVI62b1Ed0Nb27NwadwAQRKW3u3iZdAZAyDgcm+bLZ1pox8J6vSGpYk+a6o90ACt wtJfq1bCWOLAjipW7MHTnTFfmOk5iaIi1r4eKVqNZ7WR7rq5zK5LY/xFxnrRGjeNJv7J ut4Qfhg3J3y27x1SrIP4FT7Th3OXGLvpjIE1XIwx8mYErYk6TU7pJucM/5lvQzs1jZXO UOhecnPYskN4MPoUJcbIHylWdZwT/Zy11WYAGPL9VKOTjxy9MmZ6e6UyZi53cl2nQXh5 5xs1WgFLWq3ISXgP+XeEMHUIP7p6Ij4LzD3c4YoNm8pVdfzsjS/87bmZpgNgPn6ukpXK /Zfg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:references :in-reply-to:message-id:date:subject:cc:to:from:dkim-signature :arc-authentication-results; bh=F3dg2rBuHeftQz+GnE9nBfPWn2JlwPKan5iYDvanQJM=; b=khSFksPh95AAxfB728mnyXM3RfhgynFZ/HMOykRz+7tntthMHGTimpBXVvP0qx0nle gjYFFX63ncIX0ccpOFiLrBts3MAu0vk+pxwgoFC2GxZNtCqQ94+arC0ABnf468A1/qAb gRsbh2psa+BSaulSE9NicNcDGE7jTMO2XtebAitWKpu+f22/ap0wRNjokrDBMn3llYUL MBF+2v+MiPY2yGoKK3/URztKvFj7SU/AFY9/rQAHR5KD0ZM3oI8fvtwaxGgGalLy+1ZW 8pLLE1pyNgzh/wHJzA+JP/TZQiAh5u8sldRHb+KsZ0xCLlNXzZXcxE8zSrD9uWtFTvh0 pW0w== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org header.s=google header.b=aZl73lx1; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id u187si7770984pgc.451.2018.03.04.18.58.11; Sun, 04 Mar 2018 18:58:11 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@linaro.org header.s=google header.b=aZl73lx1; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932716AbeCEC6J (ORCPT + 6 others); Sun, 4 Mar 2018 21:58:09 -0500 Received: from mail-pl0-f66.google.com ([209.85.160.66]:38653 "EHLO mail-pl0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932721AbeCEC5s (ORCPT ); Sun, 4 Mar 2018 21:57:48 -0500 Received: by mail-pl0-f66.google.com with SMTP id m22-v6so5374625pls.5 for ; Sun, 04 Mar 2018 18:57:48 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; bh=hLm85M8NnqLJQ6CdQOIA5cR/DKUpVJtvHDzYvI+5O3s=; b=aZl73lx1U/4Bq6wtkqxWwvsXRrAONDgZOgzPUu4SfSFQ8mDbYd8ey4njYk8zjRptqh DppCZl7jTjySykJECIaZqkyUY14g7fzVowo7sXH/wSSLlDqOLwLm4Dmv46JHq4fYWGAQ FavwQBEkR9Gu/s2+0xyx8DpQCV4/zazl7Hl1E= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:in-reply-to:references; bh=hLm85M8NnqLJQ6CdQOIA5cR/DKUpVJtvHDzYvI+5O3s=; b=s8WxhsX0iKjsd4qsCepwAt2Wc56t0mF21keX5H5/GXghie8xPfLr+EUT1O3f5Vm7Xo 44h2UCWwz9d7KB+c2XWLkip6qNKeIcvl2Et5geStCg0xWUhxLejbQbbaLOzidsu6E22F gTBMgekOPw4GvHt7kOe6Lz6uAaqg6zNda419ltIfOaTux3PJjqOGIHw1Ep/IW8hjEgm7 K/RsnDZkIrlVft8Om4kktAItBR5LyIhimXQTD5Ug3hWTuTM9xsyW/qMcahhBMbSD+Vkv zYipl4OxkboZpZy02g9rYgUj6pZ9m9ms7F+NWdNT+mh0uBRU7+U6I/jWf6o/MSlBoE1J nT4A== X-Gm-Message-State: APf1xPBi7+EaCy/oER5MU6cWXUQ5fP9+CWE3zYFypAq8YLE55UMcBCEa rwuYMGs+WPJQr6tMmTgmDMvOmQ== X-Received: by 2002:a17:902:6985:: with SMTP id l5-v6mr12035457plk.14.1520218667626; Sun, 04 Mar 2018 18:57:47 -0800 (PST) Received: from baolinwangubtpc.spreadtrum.com ([117.18.48.82]) by smtp.gmail.com with ESMTPSA id p6sm22266571pfg.183.2018.03.04.18.57.44 (version=TLS1 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sun, 04 Mar 2018 18:57:47 -0800 (PST) From: Baolin Wang To: linus.walleij@linaro.org, robh+dt@kernel.org, mark.rutland@arm.com Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, broonie@kernel.org, baolin.wang@linaro.org, andy.shevchenko@gmail.com Subject: [PATCH v3 3/3] gpio: Add Spreadtrum PMIC EIC driver support Date: Mon, 5 Mar 2018 10:56:52 +0800 Message-Id: X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <787c9f115db06390549bfee366b7f70faf312286.1520218279.git.baolin.wang@linaro.org> References: <787c9f115db06390549bfee366b7f70faf312286.1520218279.git.baolin.wang@linaro.org> In-Reply-To: <787c9f115db06390549bfee366b7f70faf312286.1520218279.git.baolin.wang@linaro.org> References: <787c9f115db06390549bfee366b7f70faf312286.1520218279.git.baolin.wang@linaro.org> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org The Spreadtrum PMIC EIC controller contains only one bank of debounce EIC, and this bank contains 16 EICs. Each EIC can only be used as input mode, as well as supporting the debounce and the capability to trigger interrupts when detecting input signals. Signed-off-by: Baolin Wang --- Changes since v2: - Fix coding style issue. - Fix possible overflow. - Use for_each_set_bit(). Changes since v1: - New patch. --- drivers/gpio/Kconfig | 8 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-pmic-eic-sprd.c | 330 +++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 drivers/gpio/gpio-pmic-eic-sprd.c -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c937407..f642314 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -380,6 +380,14 @@ config GPIO_PL061 help Say yes here to support the PrimeCell PL061 GPIO device +config GPIO_PMIC_EIC_SPRD + tristate "Spreadtrum PMIC EIC support" + depends on MFD_SC27XX_PMIC || COMPILE_TEST + depends on OF_GPIO + select GPIOLIB_IRQCHIP + help + Say yes here to support Spreadtrum PMIC EIC device. + config GPIO_PXA bool "PXA GPIO support" depends on ARCH_PXA || ARCH_MMP diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 66eaa9e..50ba64b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16) += gpio-pci-idio-16.o obj-$(CONFIG_GPIO_PCIE_IDIO_24) += gpio-pcie-idio-24.o obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o +obj-$(CONFIG_GPIO_PMIC_EIC_SPRD) += gpio-pmic-eic-sprd.o obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o diff --git a/drivers/gpio/gpio-pmic-eic-sprd.c b/drivers/gpio/gpio-pmic-eic-sprd.c new file mode 100644 index 0000000..66d68d9 --- /dev/null +++ b/drivers/gpio/gpio-pmic-eic-sprd.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Spreadtrum Communications Inc. + * Copyright (C) 2018 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* EIC registers definition */ +#define SPRD_PMIC_EIC_DATA 0x0 +#define SPRD_PMIC_EIC_DMSK 0x4 +#define SPRD_PMIC_EIC_IEV 0x14 +#define SPRD_PMIC_EIC_IE 0x18 +#define SPRD_PMIC_EIC_RIS 0x1c +#define SPRD_PMIC_EIC_MIS 0x20 +#define SPRD_PMIC_EIC_IC 0x24 +#define SPRD_PMIC_EIC_TRIG 0x28 +#define SPRD_PMIC_EIC_CTRL0 0x40 + +/* + * The PMIC EIC controller only has one bank, and each bank now can contain + * 16 EICs. + */ +#define SPRD_PMIC_EIC_PER_BANK_NR 16 +#define SPRD_PMIC_EIC_NR SPRD_PMIC_EIC_PER_BANK_NR +#define SPRD_PMIC_EIC_DATA_MASK GENMASK(15, 0) +#define SPRD_PMIC_EIC_BIT(x) ((x) & (SPRD_PMIC_EIC_PER_BANK_NR - 1)) +#define SPRD_PMIC_EIC_DBNC_MASK GENMASK(11, 0) + +/* + * These registers are modified under the irq bus lock and cached to avoid + * unnecessary writes in bus_sync_unlock. + */ +enum { + REG_IEV, + REG_IE, + REG_TRIG, + CACHE_NR_REGS +}; + +/** + * struct sprd_pmic_eic - PMIC EIC controller + * @chip: the gpio_chip structure. + * @intc: the irq_chip structure. + * @regmap: the regmap from the parent device. + * @offset: the EIC controller's offset address of the PMIC. + * @reg: the array to cache the EIC registers. + * @buslock: for bus lock/sync and unlock. + * @irq: the interrupt number of the PMIC EIC conteroller. + */ +struct sprd_pmic_eic { + struct gpio_chip chip; + struct irq_chip intc; + struct regmap *map; + u32 offset; + u8 reg[CACHE_NR_REGS]; + struct mutex buslock; + int irq; +}; + +static void sprd_pmic_eic_update(struct gpio_chip *chip, unsigned int offset, + u16 reg, unsigned int val) +{ + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + u32 shift = SPRD_PMIC_EIC_BIT(offset); + + regmap_update_bits(pmic_eic->map, pmic_eic->offset + reg, + BIT(shift), val << shift); +} + +static int sprd_pmic_eic_read(struct gpio_chip *chip, unsigned int offset, + u16 reg) +{ + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + u32 value; + int ret; + + ret = regmap_read(pmic_eic->map, pmic_eic->offset + reg, &value); + if (ret) + return ret; + + return !!(value & BIT(SPRD_PMIC_EIC_BIT(offset))); +} + +static int sprd_pmic_eic_request(struct gpio_chip *chip, unsigned int offset) +{ + sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_DMSK, 1); + return 0; +} + +static void sprd_pmic_eic_free(struct gpio_chip *chip, unsigned int offset) +{ + sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_DMSK, 0); +} + +static int sprd_pmic_eic_get(struct gpio_chip *chip, unsigned int offset) +{ + return sprd_pmic_eic_read(chip, offset, SPRD_PMIC_EIC_DATA); +} + +static int sprd_pmic_eic_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + /* EICs are always input, nothing need to do here. */ + return 0; +} + +static void sprd_pmic_eic_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + /* EICs are always input, nothing need to do here. */ +} + +static int sprd_pmic_eic_set_debounce(struct gpio_chip *chip, + unsigned int offset, + unsigned int debounce) +{ + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + u32 reg, value; + int ret; + + reg = SPRD_PMIC_EIC_CTRL0 + SPRD_PMIC_EIC_BIT(offset) * 0x4; + ret = regmap_read(pmic_eic->map, pmic_eic->offset + reg, &value); + if (ret) + return ret; + + value &= ~SPRD_PMIC_EIC_DBNC_MASK; + value |= (debounce / 1000) & SPRD_PMIC_EIC_DBNC_MASK; + return regmap_write(pmic_eic->map, pmic_eic->offset + reg, value); +} + +static int sprd_pmic_eic_set_config(struct gpio_chip *chip, unsigned int offset, + unsigned long config) +{ + unsigned long param = pinconf_to_config_param(config); + u32 arg = pinconf_to_config_argument(config); + + if (param == PIN_CONFIG_INPUT_DEBOUNCE) + return sprd_pmic_eic_set_debounce(chip, offset, arg); + + return -ENOTSUPP; +} + +static void sprd_pmic_eic_irq_mask(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + + pmic_eic->reg[REG_IE] = 0; + pmic_eic->reg[REG_TRIG] = 0; +} + +static void sprd_pmic_eic_irq_unmask(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + + pmic_eic->reg[REG_IE] = 1; + pmic_eic->reg[REG_TRIG] = 1; +} + +static int sprd_pmic_eic_irq_set_type(struct irq_data *data, + unsigned int flow_type) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + + switch (flow_type) { + case IRQ_TYPE_LEVEL_HIGH: + pmic_eic->reg[REG_IEV] = 1; + break; + case IRQ_TYPE_LEVEL_LOW: + pmic_eic->reg[REG_IEV] = 0; + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static void sprd_pmic_eic_bus_lock(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + + mutex_lock(&pmic_eic->buslock); +} + +static void sprd_pmic_eic_bus_sync_unlock(struct irq_data *data) +{ + struct gpio_chip *chip = irq_data_get_irq_chip_data(data); + struct sprd_pmic_eic *pmic_eic = gpiochip_get_data(chip); + u32 offset = irqd_to_hwirq(data); + + /* Set irq type */ + sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IEV, + pmic_eic->reg[REG_IEV]); + /* Set irq unmask */ + sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_IE, + pmic_eic->reg[REG_IE]); + /* Generate trigger start pulse for debounce EIC */ + sprd_pmic_eic_update(chip, offset, SPRD_PMIC_EIC_TRIG, + pmic_eic->reg[REG_TRIG]); + + mutex_unlock(&pmic_eic->buslock); +} + +static irqreturn_t sprd_pmic_eic_irq_handler(int irq, void *data) +{ + struct sprd_pmic_eic *pmic_eic = data; + struct gpio_chip *chip = &pmic_eic->chip; + unsigned long status; + u32 n, girq, val; + int ret; + + ret = regmap_read(pmic_eic->map, pmic_eic->offset + SPRD_PMIC_EIC_MIS, + &val); + if (ret) + return IRQ_RETVAL(ret); + + status = val & SPRD_PMIC_EIC_DATA_MASK; + + for_each_set_bit(n, &status, chip->ngpio) { + /* Clear the interrupt */ + sprd_pmic_eic_update(chip, n, SPRD_PMIC_EIC_IC, 1); + + girq = irq_find_mapping(chip->irq.domain, n); + handle_nested_irq(girq); + } + + return IRQ_HANDLED; +} + +static int sprd_pmic_eic_probe(struct platform_device *pdev) +{ + struct gpio_irq_chip *irq; + struct sprd_pmic_eic *pmic_eic; + int ret; + + pmic_eic = devm_kzalloc(&pdev->dev, sizeof(*pmic_eic), GFP_KERNEL); + if (!pmic_eic) + return -ENOMEM; + + mutex_init(&pmic_eic->buslock); + + pmic_eic->irq = platform_get_irq(pdev, 0); + if (pmic_eic->irq < 0) { + dev_err(&pdev->dev, "Failed to get PMIC EIC interrupt.\n"); + return pmic_eic->irq; + } + + pmic_eic->map = dev_get_regmap(pdev->dev.parent, NULL); + if (!pmic_eic->map) + return -ENODEV; + + ret = of_property_read_u32(pdev->dev.of_node, "reg", &pmic_eic->offset); + if (ret) { + dev_err(&pdev->dev, "Failed to get PMIC EIC base address.\n"); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, pmic_eic->irq, NULL, + sprd_pmic_eic_irq_handler, + IRQF_TRIGGER_LOW | + IRQF_ONESHOT | IRQF_NO_SUSPEND, + dev_name(&pdev->dev), pmic_eic); + if (ret) { + dev_err(&pdev->dev, "Failed to request PMIC EIC IRQ.\n"); + return ret; + } + + pmic_eic->chip.label = dev_name(&pdev->dev); + pmic_eic->chip.ngpio = SPRD_PMIC_EIC_NR; + pmic_eic->chip.base = -1; + pmic_eic->chip.parent = &pdev->dev; + pmic_eic->chip.of_node = pdev->dev.of_node; + pmic_eic->chip.direction_input = sprd_pmic_eic_direction_input; + pmic_eic->chip.request = sprd_pmic_eic_request; + pmic_eic->chip.free = sprd_pmic_eic_free; + pmic_eic->chip.set_config = sprd_pmic_eic_set_config; + pmic_eic->chip.set = sprd_pmic_eic_set; + pmic_eic->chip.get = sprd_pmic_eic_get; + + pmic_eic->intc.name = dev_name(&pdev->dev); + pmic_eic->intc.irq_mask = sprd_pmic_eic_irq_mask; + pmic_eic->intc.irq_unmask = sprd_pmic_eic_irq_unmask; + pmic_eic->intc.irq_set_type = sprd_pmic_eic_irq_set_type; + pmic_eic->intc.irq_bus_lock = sprd_pmic_eic_bus_lock; + pmic_eic->intc.irq_bus_sync_unlock = sprd_pmic_eic_bus_sync_unlock; + pmic_eic->intc.flags = IRQCHIP_SKIP_SET_WAKE; + + irq = &pmic_eic->chip.irq; + irq->chip = &pmic_eic->intc; + irq->threaded = true; + + ret = devm_gpiochip_add_data(&pdev->dev, &pmic_eic->chip, pmic_eic); + if (ret < 0) { + dev_err(&pdev->dev, "Could not register gpiochip %d.\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pmic_eic); + return 0; +} + +static const struct of_device_id sprd_pmic_eic_of_match[] = { + { .compatible = "sprd,sc27xx-eic", }, + { /* end of list */ } +}; +MODULE_DEVICE_TABLE(of, sprd_pmic_eic_of_match); + +static struct platform_driver sprd_pmic_eic_driver = { + .probe = sprd_pmic_eic_probe, + .driver = { + .name = "sprd-pmic-eic", + .of_match_table = sprd_pmic_eic_of_match, + }, +}; + +module_platform_driver(sprd_pmic_eic_driver); + +MODULE_DESCRIPTION("Spreadtrum PMIC EIC driver"); +MODULE_LICENSE("GPL v2");