[PATCH] [RFC] pinctrl: add a driver for Energy Micro's efm32 SoCs

From: Uwe Kleine-KÃnig
Date: Thu Dec 08 2011 - 17:41:17 EST


Signed-off-by: Uwe Kleine-KÃnig <u.kleine-koenig@xxxxxxxxxxxxxx>
---
Note that there is no support yet for efm32 in mainline, so ARCH_EFM32
isn't defined.
---
drivers/pinctrl/Kconfig | 6 +
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinmux-efm32.c | 352 ++++++++++++++++++++++++++++
include/linux/platform_data/efm32-pinctl.h | 60 +++++
4 files changed, 419 insertions(+), 0 deletions(-)
create mode 100644 drivers/pinctrl/pinmux-efm32.c
create mode 100644 include/linux/platform_data/efm32-pinctl.h

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index e17e2f8..5067056 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -20,6 +20,12 @@ config DEBUG_PINCTRL
help
Say Y here to add some extra checks and diagnostics to PINCTRL calls.

+config PINMUX_EFM32
+ bool "EFM32 pinmux driver"
+ depends on ARCH_EFM32
+ default y
+ select PINMUX
+
config PINMUX_SIRF
bool "CSR SiRFprimaII pinmux driver"
depends on ARCH_PRIMA2
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 50a2e2f..61846b0 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -4,5 +4,6 @@ ccflags-$(CONFIG_DEBUG_PINCTRL) += -DDEBUG

obj-$(CONFIG_PINCTRL) += core.o
obj-$(CONFIG_PINMUX) += pinmux.o
+obj-$(CONFIG_PINMUX_EFM32) += pinmux-efm32.o
obj-$(CONFIG_PINMUX_SIRF) += pinmux-sirf.o
obj-$(CONFIG_PINMUX_U300) += pinmux-u300.o
diff --git a/drivers/pinctrl/pinmux-efm32.c b/drivers/pinctrl/pinmux-efm32.c
new file mode 100644
index 0000000..2cb1ba5
--- /dev/null
+++ b/drivers/pinctrl/pinmux-efm32.c
@@ -0,0 +1,352 @@
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/efm32-pinctl.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+#include "core.h"
+
+#define DRIVER_NAME "efm32-pinctl"
+
+#define EFM32_REG_CTRL 0x00
+#define EFM32_REG_MODEL 0x04
+#define EFM32_REG_MODEH 0x04
+#define EFM32_REG_DOUTSET 0x10
+#define EFM32_REG_DOUTCLR 0x14
+
+/*
+ * The lower 4 bits of these values go into the MODE register,
+ * the 5th into DOUT if the 6th bit is set
+ */
+#define EFM32_MODE_DISABLE 0x20
+#define EFM32_MODE_INPUT 0x21
+#define EFM32_MODE_PUSHPULL 0x04
+#define EFM32_MODE_PUSHPULL_LOW 0x24
+#define EFM32_MODE_PUSHPULL_HIGH 0x34
+
+struct efm32_pinctrl_ddata {
+ struct pinctrl_desc pinctrldesc;
+ const struct efm32_pinctl_pdata *pdata;
+ struct platform_device *pdev;
+ struct pinctrl_dev *pinctrldev;
+ void __iomem *base;
+ struct clk *clk;
+};
+
+#define efm32_pinctrl_dbg(ddata, fmt, arg...) \
+ dev_dbg(&ddata->pdev->dev, fmt, ##arg)
+
+static int efm32_pinctl_pctl_list_groups(struct pinctrl_dev *pctldev,
+ unsigned selector)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+ const struct efm32_pinctl_pdata *pdata = ddata->pdata;
+
+ if (selector >= pdata->ngroups)
+ return -EINVAL;
+ return 0;
+}
+
+static const char *efm32_pinctl_pctl_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned selector)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+ const struct efm32_pinctl_pdata *pdata = ddata->pdata;
+
+ return pdata->groups[selector]->name;
+}
+
+static int efm32_pinctl_pctl_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned selector,
+ const unsigned **pins,
+ unsigned *npins)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+ const struct efm32_pinctl_pdata *pdata = ddata->pdata;
+
+ *pins = pdata->groups[selector]->pins;
+ *npins = pdata->groups[selector]->npins;
+
+ return 0;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+static void efm32_pinctl_pctl_pin_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned offset)
+{
+ seq_printf(s, " " DRIVER_NAME);
+}
+#else
+#define efm32_pinctl_pctl_pin_dbg_show NULL
+#endif
+
+static struct pinctrl_ops efm32_pinctrl_pctlops = {
+ .list_groups = efm32_pinctl_pctl_list_groups,
+ .get_group_name = efm32_pinctl_pctl_get_group_name,
+ .get_group_pins = efm32_pinctl_pctl_get_group_pins,
+ .pin_dbg_show = efm32_pinctl_pctl_pin_dbg_show,
+};
+
+struct efm32_pmx_func {
+ const char *name;
+ const char **groups;
+ const unsigned ngroups;
+ const unsigned *mode;
+};
+
+static const char *efm32_us1_groups[] = {
+ "us1_loc0",
+ "us1_loc1",
+ "us1_loc2",
+};
+
+/* order: TX, RX, CS, CLK */
+static const unsigned efm32_us_modes[] = {
+ EFM32_MODE_PUSHPULL_HIGH, EFM32_MODE_INPUT,
+ EFM32_MODE_DISABLE, EFM32_MODE_DISABLE
+};
+
+#define EFM32_PMXFUNC(_name, num) { \
+ .name = #_name #num, \
+ .groups = efm32_ ## _name ## num ## _groups, \
+ .ngroups = ARRAY_SIZE(efm32_ ## _name ## num ## _groups),\
+ .mode = efm32_ ## _name ## _modes, \
+ }
+
+static const struct efm32_pmx_func efm32_pmx_funcs[] = {
+ EFM32_PMXFUNC(us, 1),
+};
+
+static int efm32_pinctrl_pmx_list_functions(struct pinctrl_dev *pctldev,
+ unsigned selector)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+ const struct efm32_pinctl_pdata *pdata = ddata->pdata;
+
+ if (selector >= pdata->nfuncs)
+ return -EINVAL;
+ return 0;
+}
+
+static const char *efm32_pinctrl_pmx_get_function_name(
+ struct pinctrl_dev *pctldev, unsigned selector)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+ const struct efm32_pinctl_pdata *pdata = ddata->pdata;
+
+ return pdata->funcs[selector].name;
+}
+
+static int efm32_pinctrl_pmx_get_function_groups(
+ struct pinctrl_dev *pctldev,
+ unsigned selector,
+ const char * const **groups,
+ unsigned * const ngroups)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+ const struct efm32_pinctl_pdata *pdata = ddata->pdata;
+
+ *groups = pdata->funcs[selector].groups;
+ *ngroups = pdata->funcs[selector].ngroups;
+ return 0;
+}
+
+static void efm32_pinctrl_pmx_config(struct efm32_pinctrl_ddata *ddata,
+ unsigned pin, unsigned mode)
+{
+ unsigned bank = pin / 16;
+ unsigned bankpin = pin % 16;
+ unsigned bank_regoff = bank * 0x24;
+ unsigned mode_regoff = bankpin < 8 ? EFM32_REG_MODEL : EFM32_REG_MODEH;
+ unsigned dout_regoff =
+ mode & 0x10 ? EFM32_REG_DOUTSET : EFM32_REG_DOUTCLR;
+ u32 regmode;
+
+ efm32_pinctrl_dbg(ddata, "config(%u, 0x%x)\n", pin, mode);
+
+ /*
+ * first set/unset DOUT unless the pin will be disabled. This prevents
+ * most glitches in practise.
+ */
+ if (mode & 0x20 && mode & 0xf)
+ writel(1 << bankpin, ddata->base + bank_regoff + dout_regoff);
+
+ regmode = readl(ddata->base + bank_regoff + mode_regoff);
+
+ regmode &= ~(0xf << (4 * (bankpin % 8)));
+ regmode |= (mode & 0xf) << (4 * (bankpin % 8));
+
+ efm32_pinctrl_dbg(ddata, "[%03x] <- %08x\n",
+ bank_regoff + mode_regoff, regmode);
+ writel(regmode, ddata->base + bank_regoff + mode_regoff);
+
+ if (mode & 0x20 && !(mode & 0xf))
+ writel(1 << bankpin, ddata->base + bank_regoff + dout_regoff);
+}
+
+static const struct efm32_pinctl_group *efm32_pinctrl_lookup_group(
+ struct pinctrl_dev *pctldev, const char *name)
+{
+ unsigned i;
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+ const struct efm32_pinctl_pdata *pdata = ddata->pdata;
+
+ for (i = 0; i < pdata->ngroups; ++i) {
+ if (!strcmp(pdata->groups[i]->name, name))
+ return pdata->groups[i];
+ }
+
+ return NULL;
+}
+
+static int efm32_pinctrl_pmx_enable(struct pinctrl_dev *pctldev,
+ unsigned func_selector,
+ unsigned group_selector)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+
+ const struct efm32_pmx_func *func;
+ const char *groupname;
+ const struct efm32_pinctl_group *group;
+ unsigned i;
+
+ efm32_pinctrl_dbg(ddata, "%s(%u, %u)\n",
+ __func__, func_selector, group_selector);
+
+ func = &efm32_pmx_funcs[func_selector];
+ groupname = func->groups[group_selector];
+ group = efm32_pinctrl_lookup_group(pctldev, groupname);
+
+ if (!group)
+ return -EINVAL;
+
+ for (i = 0; i < group->npins; ++i)
+ efm32_pinctrl_pmx_config(ddata, group->pins[i], func->mode[i]);
+
+ return 0;
+}
+
+static void efm32_pinctrl_pmx_disable(struct pinctrl_dev *pctldev,
+ unsigned func_selector,
+ unsigned group_selector)
+{
+ struct efm32_pinctrl_ddata *ddata = pctldev->driver_data;
+
+ const struct efm32_pmx_func *func = &efm32_pmx_funcs[func_selector];
+ const char *groupname = func->groups[group_selector];
+ const struct efm32_pinctl_group *group = efm32_pinctrl_lookup_group(pctldev, groupname);
+ unsigned i;
+
+ for (i = 0; i < group->npins; ++i)
+ efm32_pinctrl_pmx_config(ddata, group->pins[i],
+ EFM32_MODE_DISABLE);
+}
+
+static struct pinmux_ops efm32_pinctrl_pmxops = {
+ .list_functions = efm32_pinctrl_pmx_list_functions,
+ .get_function_name = efm32_pinctrl_pmx_get_function_name,
+ .get_function_groups = efm32_pinctrl_pmx_get_function_groups,
+ .enable = efm32_pinctrl_pmx_enable,
+ .disable = efm32_pinctrl_pmx_disable,
+};
+
+static int __devinit efm32_pinctrl_probe(struct platform_device *pdev)
+{
+ int ret = -ENOMEM;
+ struct efm32_pinctrl_ddata *ddata;
+ const struct resource *res;
+
+ ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
+ if (!ddata) {
+ dev_dbg(&pdev->dev, "allocating ddata failed\n");
+ goto err_ddata_kzalloc;
+ }
+
+ ddata->pdev = pdev;
+ ddata->pdata = dev_get_platdata(&pdev->dev);
+
+ if (!ddata->pdata) {
+ dev_dbg(&pdev->dev, "no platform data\n");
+ goto err_platdata;
+ }
+
+ ddata->pinctrldesc.name = DRIVER_NAME;
+ ddata->pinctrldesc.pins = ddata->pdata->pins;
+ ddata->pinctrldesc.npins = ddata->pdata->npins;
+ ddata->pinctrldesc.maxpin = ddata->pdata->npins;
+ ddata->pinctrldesc.pctlops = &efm32_pinctrl_pctlops;
+ ddata->pinctrldesc.pmxops = &efm32_pinctrl_pmxops;
+ ddata->pinctrldesc.owner = THIS_MODULE;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENODEV;
+ dev_dbg(&pdev->dev, "getting base address failed\n");
+ goto err_get_base;
+ }
+
+ ddata->base = ioremap(res->start, 0x140);
+ if (!ddata->base) {
+ ret = -ENOMEM;
+ dev_dbg(&pdev->dev, "failed to remap\n");
+ goto err_ioremap;
+ }
+
+ ddata->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ddata->clk)) {
+ ret = PTR_ERR(ddata->clk);
+ dev_dbg(&pdev->dev, "failed to get clock\n");
+ goto err_clk_get;
+ }
+
+ ret = clk_prepare(ddata->clk);
+ if (ret) {
+ dev_dbg(&pdev->dev, "failed to prepare clock\n");
+ goto err_clk_prepare;
+ }
+
+ ddata->pinctrldev = pinctrl_register(&ddata->pinctrldesc,
+ &pdev->dev, ddata);
+ if (!ddata->pinctrldev) {
+ ret = -EINVAL;
+ dev_dbg(&pdev->dev, "failed to register pinctrl device");
+
+ clk_unprepare(ddata->clk);
+err_clk_prepare:
+
+ clk_put(ddata->clk);
+err_clk_get:
+
+ iounmap(ddata->base);
+err_ioremap:
+err_get_base:
+err_platdata:
+ kfree(ddata);
+ } else
+ efm32_pinctrl_dbg(ddata, "initialized (%p, %p)\n", ddata, ddata->pinctrldev);
+
+err_ddata_kzalloc:
+ return ret;
+}
+
+static struct platform_driver efm32_pinctrl_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = efm32_pinctrl_probe,
+};
+
+static int __init efm32_pinctrl_init(void)
+{
+ return platform_driver_register(&efm32_pinctrl_driver);
+}
+arch_initcall(efm32_pinctrl_init);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@xxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("efm32 pin control");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/efm32-pinctl.h b/include/linux/platform_data/efm32-pinctl.h
new file mode 100644
index 0000000..38d0f9e6
--- /dev/null
+++ b/include/linux/platform_data/efm32-pinctl.h
@@ -0,0 +1,60 @@
+#ifndef __LINUX_PLATFORM_DATA_EFM32_PINCTL_H__
+#define __LINUX_PLATFORM_DATA_EFM32_PINCTL_H__
+/*
+ * The lower 4 bits of these values go into the MODE register,
+ * the 5th into DOUT if the 6th bit is set
+ */
+#define EFM32_MODE_DISABLE 0x20
+#define EFM32_MODE_INPUT 0x21
+#define EFM32_MODE_PUSHPULL 0x04
+#define EFM32_MODE_PUSHPULL_LOW 0x24
+#define EFM32_MODE_PUSHPULL_HIGH 0x34
+
+/**
+ * struct efm32_pinctl_group
+ * @name: name of the group
+ * @pins: list of pins that form the group
+ * @npins: length of @pins
+ */
+struct efm32_pinctl_group {
+ const char *name;
+ const unsigned *pins;
+ unsigned npins;
+};
+
+/**
+ * struct efm32_pinctl_muxfunc
+ * @name: name of the function
+ * @groups: list of groups this function can be muxed to
+ * @ngroups: length of @groups
+ * @modes: modes to set for the pins when the function is enabled
+ *
+ * All groups must have the same length and order and their length must match
+ * the length of @modes. When the function is enabled for group g, g->pins[i]
+ * gets assigned mode modes[i].
+ */
+struct efm32_pinctl_muxfunc {
+ const char *name;
+ const char *const *groups;
+ unsigned ngroups;
+ const unsigned *modes;
+};
+
+/**
+ * @pins: list of pins available
+ * @npins: length of @pins
+ * @groups: list of groups available
+ * @ngroups: length of @groups
+ * @funcs: list of functions available
+ * @nfuncs: length oflength of @funcs
+ */
+struct efm32_pinctl_pdata {
+ const struct pinctrl_pin_desc *pins;
+ unsigned npins;
+ const struct efm32_pinctl_group *const *groups;
+ unsigned ngroups;
+ const struct efm32_pinctl_muxfunc *funcs;
+ unsigned nfuncs;
+};
+
+#endif /* ifndef __LINUX_PLATFORM_DATA_EFM32_PINCTL_H__ */
--
1.7.7.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/