summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>2013-02-12 23:31:27 +0100
committerUwe Kleine-König <u.kleine-koenig@pengutronix.de>2014-10-05 10:29:39 +0200
commitc4c0c2e0a8de16cc8c10f944d51547da526fa71f (patch)
tree19cad39403e931504689302e7e971a0439f915c2
parentc4aa7fcbab00f240479bc452eda69a8d7fefe16e (diff)
downloadlinux-c4c0c2e0a8de16cc8c10f944d51547da526fa71f.tar.gz
linux-c4c0c2e0a8de16cc8c10f944d51547da526fa71f.tar.xz
[WIP] pinctrl: add support for Energy Micro's efm32 gpio module
-rw-r--r--arch/arm/Kconfig1
-rw-r--r--drivers/pinctrl/Kconfig5
-rw-r--r--drivers/pinctrl/Makefile1
-rw-r--r--drivers/pinctrl/pinctrl-efm32.c522
4 files changed, 529 insertions, 0 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 6bcd5554b6eb..9ecd48a870dc 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -425,6 +425,7 @@ config ARCH_EFM32
select GENERIC_CLOCKEVENTS
select NO_DMA
select NO_IOPORT_MAP
+ select PINCTRL
select SPARSE_IRQ
select USE_OF
help
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index bfd2c2e9f6cd..f4d7fa5844e3 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -97,6 +97,11 @@ config PINCTRL_BCM281XX
BCM28145, and BCM28155 SoCs. This driver requires the pinctrl
framework. GPIO is provided by a separate GPIO driver.
+config PINCTRL_EFM32
+ bool "Silabs efm32 pinctrl driver"
+ depends on ARCH_EFM32 || COMPILE_TEST
+ select PINMUX
+
config PINCTRL_IMX
bool
select PINMUX
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 05d227508c95..5e9fc94919a0 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_PINCTRL_AT91) += pinctrl-at91.o
obj-$(CONFIG_PINCTRL_BCM2835) += pinctrl-bcm2835.o
obj-$(CONFIG_PINCTRL_BAYTRAIL) += pinctrl-baytrail.o
obj-$(CONFIG_PINCTRL_BCM281XX) += pinctrl-bcm281xx.o
+obj-$(CONFIG_PINCTRL_EFM32) += pinctrl-efm32.o
obj-$(CONFIG_PINCTRL_IMX) += pinctrl-imx.o
obj-$(CONFIG_PINCTRL_IMX1_CORE) += pinctrl-imx1-core.o
obj-$(CONFIG_PINCTRL_IMX1) += pinctrl-imx1.o
diff --git a/drivers/pinctrl/pinctrl-efm32.c b/drivers/pinctrl/pinctrl-efm32.c
new file mode 100644
index 000000000000..80460abf723e
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-efm32.c
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2014 Pengutronix
+ * Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/irqdomain.h>
+#include <linux/interrupt.h>
+#include <linux/gfp.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/clk.h>
+
+#include <asm/gpio.h>
+#include <asm/io.h>
+
+#define DRIVER_NAME "efm32-pinctrl"
+
+#define PORT_OFFSET 0x024
+
+#define REG_PORT_CTRL(p) (0x024 * (p))
+#define REG_PORT_MODEL(p) (0x024 * (p) + 0x004)
+#define REG_PORT_MODEH(p) (0x024 * (p) + 0x008)
+#define REG_PORT_DOUT(p) (0x024 * (p) + 0x00c)
+#define REG_PORT_DOUTSET(p) (0x024 * (p) + 0x010)
+#define REG_PORT_DOUTCLR(p) (0x024 * (p) + 0x014)
+#define REG_PORT_DIN(p) (0x024 * (p) + 0x01c)
+
+#define REG_EXTIPSELL 0x100
+#define REG_EXTIPSELH 0x104
+#define REG_EXTIRISE 0x108
+#define REG_EXTIFALL 0x10c
+#define REG_IEN 0x110
+#define REG_IF 0x114
+#define REG_IFS 0x118
+#define REG_IFC 0x11c
+#define REG_ROUTE 0x120
+
+struct efm32_pinctrl_ddata {
+ void __iomem *base;
+ struct clk *clk;
+ struct pinctrl_dev *pinctrl;
+
+ unsigned int irq_even, irq_odd;
+ struct irq_domain *irq_domain;
+ struct gpio_chip chip;
+ unsigned assigned_irqpins;
+};
+
+#define to_ddata(_chip) container_of(_chip, struct efm32_pinctrl_ddata, chip)
+
+static unsigned efm32_pinctrl_get_mode(struct efm32_pinctrl_ddata *ddata,
+ unsigned pin, unsigned port)
+{
+ return (readl(ddata->base + (pin < 8 ? REG_PORT_MODEL(port) : REG_PORT_MODEH(port))) >> (4 * (pin & 7))) & 0xf;
+}
+
+static int efm32_pinctrl_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ struct efm32_pinctrl_ddata *ddata = to_ddata(chip);
+ unsigned pin = offset % 16;
+ unsigned port = offset / 16;
+ unsigned mode;
+ int ret;
+
+ mode = efm32_pinctrl_get_mode(ddata, pin, port);
+
+ /*
+ * XXX: don't reconfigure, needs to be resolved in combination with a
+ * pinmux driver
+ */
+ if (mode > 0 && mode < 4)
+ ret = 0;
+ else
+ ret = -EIO;
+
+ return ret;
+
+}
+
+static int efm32_pinctrl_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct efm32_pinctrl_ddata *ddata = to_ddata(chip);
+ unsigned pin = offset % 16;
+ unsigned port = offset / 16;
+ unsigned din = readl(ddata->base + REG_PORT_DIN(port));
+
+ return din & (1 << pin);
+}
+
+static int efm32_pinctrl_direction_output(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct efm32_pinctrl_ddata *ddata = to_ddata(chip);
+ unsigned port = offset / 16;
+ unsigned pin = offset % 16;
+ unsigned mode;
+ int ret;
+ size_t regoffset = value ? REG_PORT_DOUTSET(port) : REG_PORT_DOUTCLR(port);
+
+ mode = efm32_pinctrl_get_mode(ddata, pin, port);
+ /*
+ * XXX: don't reconfigure, needs to be resolved in combination with a
+ * pinmux driver
+ */
+ if (mode >= 4)
+ ret = 0;
+ else
+ ret = -EIO;
+
+ writel(1 << pin, ddata->base + regoffset);
+
+ return ret;
+}
+
+static void efm32_pinctrl_set(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct efm32_pinctrl_ddata *ddata = to_ddata(chip);
+ unsigned pin = offset % 16;
+ unsigned port = offset / 16;
+
+ writel(1 << pin, ddata->base +
+ (value ? REG_PORT_DOUTSET(port) : REG_PORT_DOUTCLR(port)));
+}
+
+static int efm32_pinctrl_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct efm32_pinctrl_ddata *ddata = to_ddata(chip);
+ unsigned pin = offset % 16;
+ unsigned port = offset / 16;
+ unsigned extipsel_offset = pin < 8 ? REG_EXTIPSELL : REG_EXTIPSELL;
+ unsigned extipsel;
+ unsigned extipsel_shift = (pin % 8) * 4;
+
+ extipsel = readl(ddata->base + extipsel_offset);
+
+ if (ddata->assigned_irqpins & (1 << pin)) {
+ if (((extipsel >> extipsel_shift) & 0x7) != port)
+ return -EBUSY;
+ }
+
+ extipsel &= ~(0x7 << extipsel_shift);
+ extipsel |= port << extipsel_shift;
+ ddata->assigned_irqpins |= 1 << pin;
+ writel(extipsel, ddata->base + extipsel_offset);
+
+ return irq_create_mapping(ddata->irq_domain, offset % 16);
+}
+
+static void efm32_pinctrl_handler(unsigned int irq, struct irq_desc *desc,
+ unsigned mask)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct efm32_pinctrl_ddata *ddata = irq_get_handler_data(irq);
+ unsigned flag;
+
+ chained_irq_enter(chip, desc);
+
+ flag = readl(ddata->base + REG_IF) & mask;
+
+ while (flag) {
+ int line = __fls(flag);
+
+ generic_handle_irq(irq_create_mapping(ddata->irq_domain, line));
+
+ flag &= ~(1 << line);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void efm32_pinctrl_handler_even(unsigned int irq, struct irq_desc *desc)
+{
+ efm32_pinctrl_handler(irq, desc, 0x55555555U);
+}
+
+static void efm32_pinctrl_handler_odd(unsigned int irq, struct irq_desc *desc)
+{
+ efm32_pinctrl_handler(irq, desc, 0xaaaaaaaaU);
+}
+
+static void efm32_pinctrl_irq_ack(struct irq_data *data)
+{
+ struct efm32_pinctrl_ddata *ddata = irq_get_chip_data(data->irq);
+
+ writel(1 << data->hwirq, ddata->base + REG_IFC);
+}
+
+static void efm32_pinctrl_irq_mask(struct irq_data *data)
+{
+ struct efm32_pinctrl_ddata *ddata = irq_get_chip_data(data->irq);
+ unsigned ien;
+
+ ien = readl(ddata->base + REG_IEN);
+ ien &= ~(1 << data->hwirq);
+ writel(ien, ddata->base + REG_IEN);
+}
+
+static void efm32_pinctrl_irq_level_unmask(struct irq_data *data)
+{
+ struct efm32_pinctrl_ddata *ddata = irq_get_chip_data(data->irq);
+ unsigned ien;
+ u32 extipsel, level, active_level;
+ unsigned port;
+
+ /* if gpio is still active, retrigger irq */
+ if (data->hwirq <= 7)
+ extipsel = readl(ddata->base + REG_EXTIPSELL);
+ else
+ extipsel = readl(ddata->base + REG_EXTIPSELH);
+
+ /* XXX: clear irq here? */
+
+ port = (extipsel >> 4 * (data->hwirq & 7)) & 0x7;
+ active_level = readl(ddata->base + REG_EXTIRISE) & (1 << data->hwirq);
+ level = readl(ddata->base + REG_PORT_DIN(port)) & (1 << data->hwirq);
+ pr_debug("%s: port = %c, active_level = %u, level = %u\n",
+ __func__, 'A' + port, active_level, level);
+ if (level == active_level)
+ writel(1 << data->hwirq, ddata->base + REG_IFS);
+
+ ien = readl(ddata->base + REG_IEN);
+ ien |= 1 << data->hwirq;
+ writel(ien, ddata->base + REG_IEN);
+
+}
+
+static struct irq_chip efm32_pinctrl_level_irqchip = {
+ .irq_ack = efm32_pinctrl_irq_ack,
+ .irq_mask = efm32_pinctrl_irq_mask,
+ .irq_unmask = efm32_pinctrl_irq_level_unmask,
+ //.irq_set_type = efm32_pinctrl_set_type,
+};
+
+static int efm32_pinctrl_irq_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct efm32_pinctrl_ddata *ddata = d->host_data;
+
+ irq_set_chip_data(virq, ddata);
+ irq_set_chip_and_handler(virq, &efm32_pinctrl_level_irqchip, handle_level_irq);
+
+ set_irq_flags(virq, IRQF_VALID | IRQF_PROBE);
+
+ return 0;
+}
+
+static const struct irq_domain_ops efm32_pinctrl_irq_domain_ops = {
+ .map = efm32_pinctrl_irq_map,
+};
+
+static const struct pinctrl_pin_desc efm32_pins[] = {
+ PINCTRL_PIN(0, "PA0"),
+ PINCTRL_PIN(1, "PA1"),
+ PINCTRL_PIN(2, "PA2"),
+ PINCTRL_PIN(3, "PA3"),
+ PINCTRL_PIN(4, "PA4"),
+ PINCTRL_PIN(5, "PA5"),
+ PINCTRL_PIN(6, "PA6"),
+ PINCTRL_PIN(7, "PA7"),
+ PINCTRL_PIN(8, "PA8"),
+ PINCTRL_PIN(9, "PA9"),
+ PINCTRL_PIN(10, "PA10"),
+ PINCTRL_PIN(11, "PA11"),
+ PINCTRL_PIN(12, "PA12"),
+ PINCTRL_PIN(13, "PA13"),
+ PINCTRL_PIN(14, "PA14"),
+ PINCTRL_PIN(15, "PA15"),
+ PINCTRL_PIN(16, "PB0"),
+ PINCTRL_PIN(17, "PB1"),
+ PINCTRL_PIN(18, "PB2"),
+ PINCTRL_PIN(19, "PB3"),
+ PINCTRL_PIN(20, "PB4"),
+ PINCTRL_PIN(21, "PB5"),
+ PINCTRL_PIN(22, "PB6"),
+ PINCTRL_PIN(23, "PB7"),
+ PINCTRL_PIN(24, "PB8"),
+ PINCTRL_PIN(25, "PB9"),
+ PINCTRL_PIN(26, "PB10"),
+ PINCTRL_PIN(27, "PB11"),
+ PINCTRL_PIN(28, "PB12"),
+ PINCTRL_PIN(29, "PB13"),
+ PINCTRL_PIN(30, "PB14"),
+ PINCTRL_PIN(31, "PB15"),
+ PINCTRL_PIN(32, "PC0"),
+ PINCTRL_PIN(33, "PC1"),
+ PINCTRL_PIN(34, "PC2"),
+ PINCTRL_PIN(35, "PC3"),
+ PINCTRL_PIN(36, "PC4"),
+ PINCTRL_PIN(37, "PC5"),
+ PINCTRL_PIN(38, "PC6"),
+ PINCTRL_PIN(39, "PC7"),
+ PINCTRL_PIN(40, "PC8"),
+ PINCTRL_PIN(41, "PC9"),
+ PINCTRL_PIN(42, "PC10"),
+ PINCTRL_PIN(43, "PC11"),
+ PINCTRL_PIN(44, "PC12"),
+ PINCTRL_PIN(45, "PC13"),
+ PINCTRL_PIN(46, "PC14"),
+ PINCTRL_PIN(47, "PC15"),
+ PINCTRL_PIN(48, "PD0"),
+ PINCTRL_PIN(49, "PD1"),
+ PINCTRL_PIN(50, "PD2"),
+ PINCTRL_PIN(51, "PD3"),
+ PINCTRL_PIN(52, "PD4"),
+ PINCTRL_PIN(53, "PD5"),
+ PINCTRL_PIN(54, "PD6"),
+ PINCTRL_PIN(55, "PD7"),
+ PINCTRL_PIN(56, "PD8"),
+ PINCTRL_PIN(57, "PD9"),
+ PINCTRL_PIN(58, "PD10"),
+ PINCTRL_PIN(59, "PD11"),
+ PINCTRL_PIN(60, "PD12"),
+ PINCTRL_PIN(61, "PD13"),
+ PINCTRL_PIN(62, "PD14"),
+ PINCTRL_PIN(63, "PD15"),
+ PINCTRL_PIN(64, "PE0"),
+ PINCTRL_PIN(65, "PE1"),
+ PINCTRL_PIN(66, "PE2"),
+ PINCTRL_PIN(67, "PE3"),
+ PINCTRL_PIN(68, "PE4"),
+ PINCTRL_PIN(69, "PE5"),
+ PINCTRL_PIN(70, "PE6"),
+ PINCTRL_PIN(71, "PE7"),
+ PINCTRL_PIN(72, "PE8"),
+ PINCTRL_PIN(73, "PE9"),
+ PINCTRL_PIN(74, "PE10"),
+ PINCTRL_PIN(75, "PE11"),
+ PINCTRL_PIN(76, "PE12"),
+ PINCTRL_PIN(77, "PE13"),
+ PINCTRL_PIN(78, "PE14"),
+ PINCTRL_PIN(79, "PE15"),
+ PINCTRL_PIN(80, "PF0"),
+ PINCTRL_PIN(81, "PF1"),
+ PINCTRL_PIN(82, "PF2"),
+ PINCTRL_PIN(83, "PF3"),
+ PINCTRL_PIN(84, "PF4"),
+ PINCTRL_PIN(85, "PF5"),
+ PINCTRL_PIN(86, "PF6"),
+ PINCTRL_PIN(87, "PF7"),
+ PINCTRL_PIN(88, "PF8"),
+ PINCTRL_PIN(89, "PF9"),
+ PINCTRL_PIN(90, "PF10"),
+ PINCTRL_PIN(91, "PF11"),
+ PINCTRL_PIN(92, "PF12"),
+ PINCTRL_PIN(93, "PF13"),
+ PINCTRL_PIN(94, "PF14"),
+ PINCTRL_PIN(95, "PF15"),
+};
+
+static int efm32_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ return 0;
+}
+
+const char *efm32_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned selector)
+{
+ BUG();
+ return "";
+}
+
+int efm32_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned selector, const unsigned **pins,
+ unsigned *num_pins)
+{
+ BUG();
+ return 0;
+}
+
+static struct pinctrl_desc efm32_pinctldesc = {
+ .name = DRIVER_NAME,
+ .pins = efm32_pins,
+ .npins = ARRAY_SIZE(efm32_pins),
+ .pctlops = &(const struct pinctrl_ops){
+ .get_groups_count = efm32_pinctrl_get_groups_count,
+ .get_group_name = efm32_pinctrl_get_group_name,
+ .get_group_pins = efm32_pinctrl_get_group_pins,
+ },
+ .owner = THIS_MODULE,
+};
+
+int efm32_pinctrl_probe(struct platform_device *pdev)
+{
+ struct efm32_pinctrl_ddata *ddata;
+ struct resource *res;
+ int irq_even, irq_odd;
+ int ret;
+
+ pr_info("uaaaaaaaaaaaaaaaaaaaaaaa\n");
+ ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to determine base address\n");
+ return -ENODEV;
+ }
+
+ if (resource_size(res) < 0x140) {
+ dev_err(&pdev->dev, "memory resource too small\n");
+ return -EINVAL;
+ }
+
+ ddata->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ddata->clk)) {
+ ret = PTR_ERR(ddata->clk);
+ dev_err(&pdev->dev, "can't get clock (%d)\n", ret);
+ return ret;
+ }
+
+ irq_even = platform_get_irq(pdev, 0);
+ irq_odd = platform_get_irq(pdev, 1);
+ if (irq_even <= 0 || irq_odd <= 0) {
+ dev_err(&pdev->dev, "can't get irq numbers (%d, %d)\n",
+ irq_even, irq_odd);
+ return -ENOENT;
+ }
+ ddata->irq_even = irq_even;
+ ddata->irq_odd = irq_odd;
+
+ ddata->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ddata->base))
+ return PTR_ERR(ddata->base);
+
+ ret = clk_prepare_enable(ddata->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable clock\n");
+ goto err_clk_enable;
+ }
+
+ pr_info("%s: base = %p\n", __func__, ddata->base);
+ /* disable and clear irqs */
+ writel(0, ddata->base + REG_IEN);
+ writel(0xffff, ddata->base + REG_IFC);
+
+ irq_set_handler_data(ddata->irq_even, ddata);
+ irq_set_chained_handler(ddata->irq_even, efm32_pinctrl_handler_even);
+ irq_set_handler_data(ddata->irq_odd, ddata);
+ irq_set_chained_handler(ddata->irq_odd, efm32_pinctrl_handler_odd);
+
+ ddata->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+ 16, &efm32_pinctrl_irq_domain_ops, ddata);
+ if (!ddata->irq_domain) {
+ dev_err(&pdev->dev, "failed to add irq_domain\n");
+ goto err_add_irq_domain;
+ }
+
+ ddata->chip.label = DRIVER_NAME;
+ ddata->chip.dev = &pdev->dev;
+ ddata->chip.owner = THIS_MODULE;
+
+ //ddata->chip.get_direction
+ ddata->chip.direction_input = efm32_pinctrl_direction_input;
+ ddata->chip.get = efm32_pinctrl_get;
+ ddata->chip.direction_output = efm32_pinctrl_direction_output;
+ ddata->chip.set = efm32_pinctrl_set;
+ ddata->chip.to_irq = efm32_pinctrl_to_irq;
+ ddata->chip.base = -1;
+ ddata->chip.ngpio = 96;
+ ddata->chip.can_sleep = 0;
+
+ ddata->pinctrl = pinctrl_register(&efm32_pinctldesc, &pdev->dev, ddata);
+ if (!ddata->pinctrl) {
+ dev_err(&pdev->dev, "Failed to register pinctrl\n");
+ ret = -ENOMEM; // XXX?
+ goto err_pinctrl_register;
+ }
+
+ ret = gpiochip_add(&ddata->chip);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register gpiochip\n");
+ pinctrl_unregister(ddata->pinctrl);
+
+err_pinctrl_register:
+
+ irq_domain_remove(ddata->irq_domain);
+err_add_irq_domain:
+
+ irq_set_chained_handler(ddata->irq_even, NULL);
+ irq_set_chained_handler(ddata->irq_odd, NULL);
+
+err_clk_enable:
+ clk_disable(ddata->clk);
+ }
+
+ return ret;
+}
+
+static const struct of_device_id efm32_pinctrl_dt_ids[] = {
+ { .compatible = "energymicro,efm32-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, efm32_pinctrl_dt_ids);
+
+static struct platform_driver efm32_pinctrl_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = efm32_pinctrl_dt_ids,
+ },
+
+ .probe = efm32_pinctrl_probe,
+ //XXX .remove =
+};
+module_platform_driver(efm32_pinctrl_driver);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
+MODULE_DESCRIPTION("EFM32 pinctrl driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);