| /* |
| * pin-controller/pin-mux/pin-config/gpio-driver for Samsung's EXYNOS5440 SoC. |
| * |
| * Copyright (c) 2012 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/err.h> |
| #include <linux/gpio/driver.h> |
| #include <linux/device.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/pinctrl/pinmux.h> |
| #include <linux/pinctrl/pinconf.h> |
| #include <linux/interrupt.h> |
| #include <linux/irqdomain.h> |
| #include <linux/of_irq.h> |
| #include "../core.h" |
| |
| /* EXYNOS5440 GPIO and Pinctrl register offsets */ |
| #define GPIO_MUX 0x00 |
| #define GPIO_IE 0x04 |
| #define GPIO_INT 0x08 |
| #define GPIO_TYPE 0x0C |
| #define GPIO_VAL 0x10 |
| #define GPIO_OE 0x14 |
| #define GPIO_IN 0x18 |
| #define GPIO_PE 0x1C |
| #define GPIO_PS 0x20 |
| #define GPIO_SR 0x24 |
| #define GPIO_DS0 0x28 |
| #define GPIO_DS1 0x2C |
| |
| #define EXYNOS5440_MAX_PINS 23 |
| #define EXYNOS5440_MAX_GPIO_INT 8 |
| #define PIN_NAME_LENGTH 10 |
| |
| #define GROUP_SUFFIX "-grp" |
| #define FUNCTION_SUFFIX "-mux" |
| |
| /* |
| * pin configuration type and its value are packed together into a 16-bits. |
| * The upper 8-bits represent the configuration type and the lower 8-bits |
| * hold the value of the configuration type. |
| */ |
| #define PINCFG_TYPE_MASK 0xFF |
| #define PINCFG_VALUE_SHIFT 8 |
| #define PINCFG_VALUE_MASK (0xFF << PINCFG_VALUE_SHIFT) |
| #define PINCFG_PACK(type, value) (((value) << PINCFG_VALUE_SHIFT) | type) |
| #define PINCFG_UNPACK_TYPE(cfg) ((cfg) & PINCFG_TYPE_MASK) |
| #define PINCFG_UNPACK_VALUE(cfg) (((cfg) & PINCFG_VALUE_MASK) >> \ |
| PINCFG_VALUE_SHIFT) |
| |
| /** |
| * enum pincfg_type - possible pin configuration types supported. |
| * @PINCFG_TYPE_PUD: Pull up/down configuration. |
| * @PINCFG_TYPE_DRV: Drive strength configuration. |
| * @PINCFG_TYPE_SKEW_RATE: Skew rate configuration. |
| * @PINCFG_TYPE_INPUT_TYPE: Pin input type configuration. |
| */ |
| enum pincfg_type { |
| PINCFG_TYPE_PUD, |
| PINCFG_TYPE_DRV, |
| PINCFG_TYPE_SKEW_RATE, |
| PINCFG_TYPE_INPUT_TYPE |
| }; |
| |
| /** |
| * struct exynos5440_pin_group: represent group of pins for pincfg setting. |
| * @name: name of the pin group, used to lookup the group. |
| * @pins: the pins included in this group. |
| * @num_pins: number of pins included in this group. |
| */ |
| struct exynos5440_pin_group { |
| const char *name; |
| const unsigned int *pins; |
| u8 num_pins; |
| }; |
| |
| /** |
| * struct exynos5440_pmx_func: represent a pin function. |
| * @name: name of the pin function, used to lookup the function. |
| * @groups: one or more names of pin groups that provide this function. |
| * @num_groups: number of groups included in @groups. |
| * @function: the function number to be programmed when selected. |
| */ |
| struct exynos5440_pmx_func { |
| const char *name; |
| const char **groups; |
| u8 num_groups; |
| unsigned long function; |
| }; |
| |
| /** |
| * struct exynos5440_pinctrl_priv_data: driver's private runtime data. |
| * @reg_base: ioremapped based address of the register space. |
| * @gc: gpio chip registered with gpiolib. |
| * @pin_groups: list of pin groups parsed from device tree. |
| * @nr_groups: number of pin groups available. |
| * @pmx_functions: list of pin functions parsed from device tree. |
| * @nr_functions: number of pin functions available. |
| * @range: gpio range to register with pinctrl |
| */ |
| struct exynos5440_pinctrl_priv_data { |
| void __iomem *reg_base; |
| struct gpio_chip *gc; |
| struct irq_domain *irq_domain; |
| |
| const struct exynos5440_pin_group *pin_groups; |
| unsigned int nr_groups; |
| const struct exynos5440_pmx_func *pmx_functions; |
| unsigned int nr_functions; |
| struct pinctrl_gpio_range range; |
| }; |
| |
| /** |
| * struct exynos5440_gpio_intr_data: private data for gpio interrupts. |
| * @priv: driver's private runtime data. |
| * @gpio_int: gpio interrupt number. |
| */ |
| struct exynos5440_gpio_intr_data { |
| struct exynos5440_pinctrl_priv_data *priv; |
| unsigned int gpio_int; |
| }; |
| |
| /* list of all possible config options supported */ |
| static struct pin_config { |
| char *prop_cfg; |
| unsigned int cfg_type; |
| } pcfgs[] = { |
| { "samsung,exynos5440-pin-pud", PINCFG_TYPE_PUD }, |
| { "samsung,exynos5440-pin-drv", PINCFG_TYPE_DRV }, |
| { "samsung,exynos5440-pin-skew-rate", PINCFG_TYPE_SKEW_RATE }, |
| { "samsung,exynos5440-pin-input-type", PINCFG_TYPE_INPUT_TYPE }, |
| }; |
| |
| /* check if the selector is a valid pin group selector */ |
| static int exynos5440_get_group_count(struct pinctrl_dev *pctldev) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| return priv->nr_groups; |
| } |
| |
| /* return the name of the group selected by the group selector */ |
| static const char *exynos5440_get_group_name(struct pinctrl_dev *pctldev, |
| unsigned selector) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| return priv->pin_groups[selector].name; |
| } |
| |
| /* return the pin numbers associated with the specified group */ |
| static int exynos5440_get_group_pins(struct pinctrl_dev *pctldev, |
| unsigned selector, const unsigned **pins, unsigned *num_pins) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| *pins = priv->pin_groups[selector].pins; |
| *num_pins = priv->pin_groups[selector].num_pins; |
| return 0; |
| } |
| |
| /* create pinctrl_map entries by parsing device tree nodes */ |
| static int exynos5440_dt_node_to_map(struct pinctrl_dev *pctldev, |
| struct device_node *np, struct pinctrl_map **maps, |
| unsigned *nmaps) |
| { |
| struct device *dev = pctldev->dev; |
| struct pinctrl_map *map; |
| unsigned long *cfg = NULL; |
| char *gname, *fname; |
| int cfg_cnt = 0, map_cnt = 0, idx = 0; |
| |
| /* count the number of config options specfied in the node */ |
| for (idx = 0; idx < ARRAY_SIZE(pcfgs); idx++) |
| if (of_find_property(np, pcfgs[idx].prop_cfg, NULL)) |
| cfg_cnt++; |
| |
| /* |
| * Find out the number of map entries to create. All the config options |
| * can be accomadated into a single config map entry. |
| */ |
| if (cfg_cnt) |
| map_cnt = 1; |
| if (of_find_property(np, "samsung,exynos5440-pin-function", NULL)) |
| map_cnt++; |
| if (!map_cnt) { |
| dev_err(dev, "node %s does not have either config or function " |
| "configurations\n", np->name); |
| return -EINVAL; |
| } |
| |
| /* Allocate memory for pin-map entries */ |
| map = kzalloc(sizeof(*map) * map_cnt, GFP_KERNEL); |
| if (!map) |
| return -ENOMEM; |
| *nmaps = 0; |
| |
| /* |
| * Allocate memory for pin group name. The pin group name is derived |
| * from the node name from which these map entries are be created. |
| */ |
| gname = kasprintf(GFP_KERNEL, "%s%s", np->name, GROUP_SUFFIX); |
| if (!gname) |
| goto free_map; |
| |
| /* |
| * don't have config options? then skip over to creating function |
| * map entries. |
| */ |
| if (!cfg_cnt) |
| goto skip_cfgs; |
| |
| /* Allocate memory for config entries */ |
| cfg = kzalloc(sizeof(*cfg) * cfg_cnt, GFP_KERNEL); |
| if (!cfg) |
| goto free_gname; |
| |
| /* Prepare a list of config settings */ |
| for (idx = 0, cfg_cnt = 0; idx < ARRAY_SIZE(pcfgs); idx++) { |
| u32 value; |
| if (!of_property_read_u32(np, pcfgs[idx].prop_cfg, &value)) |
| cfg[cfg_cnt++] = |
| PINCFG_PACK(pcfgs[idx].cfg_type, value); |
| } |
| |
| /* create the config map entry */ |
| map[*nmaps].data.configs.group_or_pin = gname; |
| map[*nmaps].data.configs.configs = cfg; |
| map[*nmaps].data.configs.num_configs = cfg_cnt; |
| map[*nmaps].type = PIN_MAP_TYPE_CONFIGS_GROUP; |
| *nmaps += 1; |
| |
| skip_cfgs: |
| /* create the function map entry */ |
| if (of_find_property(np, "samsung,exynos5440-pin-function", NULL)) { |
| fname = kasprintf(GFP_KERNEL, |
| "%s%s", np->name, FUNCTION_SUFFIX); |
| if (!fname) |
| goto free_cfg; |
| |
| map[*nmaps].data.mux.group = gname; |
| map[*nmaps].data.mux.function = fname; |
| map[*nmaps].type = PIN_MAP_TYPE_MUX_GROUP; |
| *nmaps += 1; |
| } |
| |
| *maps = map; |
| return 0; |
| |
| free_cfg: |
| kfree(cfg); |
| free_gname: |
| kfree(gname); |
| free_map: |
| kfree(map); |
| return -ENOMEM; |
| } |
| |
| /* free the memory allocated to hold the pin-map table */ |
| static void exynos5440_dt_free_map(struct pinctrl_dev *pctldev, |
| struct pinctrl_map *map, unsigned num_maps) |
| { |
| int idx; |
| |
| for (idx = 0; idx < num_maps; idx++) { |
| if (map[idx].type == PIN_MAP_TYPE_MUX_GROUP) { |
| kfree(map[idx].data.mux.function); |
| if (!idx) |
| kfree(map[idx].data.mux.group); |
| } else if (map->type == PIN_MAP_TYPE_CONFIGS_GROUP) { |
| kfree(map[idx].data.configs.configs); |
| if (!idx) |
| kfree(map[idx].data.configs.group_or_pin); |
| } |
| } |
| |
| kfree(map); |
| } |
| |
| /* list of pinctrl callbacks for the pinctrl core */ |
| static const struct pinctrl_ops exynos5440_pctrl_ops = { |
| .get_groups_count = exynos5440_get_group_count, |
| .get_group_name = exynos5440_get_group_name, |
| .get_group_pins = exynos5440_get_group_pins, |
| .dt_node_to_map = exynos5440_dt_node_to_map, |
| .dt_free_map = exynos5440_dt_free_map, |
| }; |
| |
| /* check if the selector is a valid pin function selector */ |
| static int exynos5440_get_functions_count(struct pinctrl_dev *pctldev) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| return priv->nr_functions; |
| } |
| |
| /* return the name of the pin function specified */ |
| static const char *exynos5440_pinmux_get_fname(struct pinctrl_dev *pctldev, |
| unsigned selector) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| return priv->pmx_functions[selector].name; |
| } |
| |
| /* return the groups associated for the specified function selector */ |
| static int exynos5440_pinmux_get_groups(struct pinctrl_dev *pctldev, |
| unsigned selector, const char * const **groups, |
| unsigned * const num_groups) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| *groups = priv->pmx_functions[selector].groups; |
| *num_groups = priv->pmx_functions[selector].num_groups; |
| return 0; |
| } |
| |
| /* enable or disable a pinmux function */ |
| static void exynos5440_pinmux_setup(struct pinctrl_dev *pctldev, unsigned selector, |
| unsigned group, bool enable) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| void __iomem *base; |
| u32 function; |
| u32 data; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| base = priv->reg_base; |
| function = priv->pmx_functions[selector].function; |
| |
| data = readl(base + GPIO_MUX); |
| if (enable) |
| data |= (1 << function); |
| else |
| data &= ~(1 << function); |
| writel(data, base + GPIO_MUX); |
| } |
| |
| /* enable a specified pinmux by writing to registers */ |
| static int exynos5440_pinmux_set_mux(struct pinctrl_dev *pctldev, |
| unsigned selector, |
| unsigned group) |
| { |
| exynos5440_pinmux_setup(pctldev, selector, group, true); |
| return 0; |
| } |
| |
| /* |
| * The calls to gpio_direction_output() and gpio_direction_input() |
| * leads to this function call (via the pinctrl_gpio_direction_{input|output}() |
| * function called from the gpiolib interface). |
| */ |
| static int exynos5440_pinmux_gpio_set_direction(struct pinctrl_dev *pctldev, |
| struct pinctrl_gpio_range *range, unsigned offset, bool input) |
| { |
| return 0; |
| } |
| |
| /* list of pinmux callbacks for the pinmux vertical in pinctrl core */ |
| static const struct pinmux_ops exynos5440_pinmux_ops = { |
| .get_functions_count = exynos5440_get_functions_count, |
| .get_function_name = exynos5440_pinmux_get_fname, |
| .get_function_groups = exynos5440_pinmux_get_groups, |
| .set_mux = exynos5440_pinmux_set_mux, |
| .gpio_set_direction = exynos5440_pinmux_gpio_set_direction, |
| }; |
| |
| /* set the pin config settings for a specified pin */ |
| static int exynos5440_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin, |
| unsigned long *configs, |
| unsigned num_configs) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| void __iomem *base; |
| enum pincfg_type cfg_type; |
| u32 cfg_value; |
| u32 data; |
| int i; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| base = priv->reg_base; |
| |
| for (i = 0; i < num_configs; i++) { |
| cfg_type = PINCFG_UNPACK_TYPE(configs[i]); |
| cfg_value = PINCFG_UNPACK_VALUE(configs[i]); |
| |
| switch (cfg_type) { |
| case PINCFG_TYPE_PUD: |
| /* first set pull enable/disable bit */ |
| data = readl(base + GPIO_PE); |
| data &= ~(1 << pin); |
| if (cfg_value) |
| data |= (1 << pin); |
| writel(data, base + GPIO_PE); |
| |
| /* then set pull up/down bit */ |
| data = readl(base + GPIO_PS); |
| data &= ~(1 << pin); |
| if (cfg_value == 2) |
| data |= (1 << pin); |
| writel(data, base + GPIO_PS); |
| break; |
| |
| case PINCFG_TYPE_DRV: |
| /* set the first bit of the drive strength */ |
| data = readl(base + GPIO_DS0); |
| data &= ~(1 << pin); |
| data |= ((cfg_value & 1) << pin); |
| writel(data, base + GPIO_DS0); |
| cfg_value >>= 1; |
| |
| /* set the second bit of the driver strength */ |
| data = readl(base + GPIO_DS1); |
| data &= ~(1 << pin); |
| data |= ((cfg_value & 1) << pin); |
| writel(data, base + GPIO_DS1); |
| break; |
| case PINCFG_TYPE_SKEW_RATE: |
| data = readl(base + GPIO_SR); |
| data &= ~(1 << pin); |
| data |= ((cfg_value & 1) << pin); |
| writel(data, base + GPIO_SR); |
| break; |
| case PINCFG_TYPE_INPUT_TYPE: |
| data = readl(base + GPIO_TYPE); |
| data &= ~(1 << pin); |
| data |= ((cfg_value & 1) << pin); |
| writel(data, base + GPIO_TYPE); |
| break; |
| default: |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| } /* for each config */ |
| |
| return 0; |
| } |
| |
| /* get the pin config settings for a specified pin */ |
| static int exynos5440_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin, |
| unsigned long *config) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| void __iomem *base; |
| enum pincfg_type cfg_type = PINCFG_UNPACK_TYPE(*config); |
| u32 data; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| base = priv->reg_base; |
| |
| switch (cfg_type) { |
| case PINCFG_TYPE_PUD: |
| data = readl(base + GPIO_PE); |
| data = (data >> pin) & 1; |
| if (!data) |
| *config = 0; |
| else |
| *config = ((readl(base + GPIO_PS) >> pin) & 1) + 1; |
| break; |
| case PINCFG_TYPE_DRV: |
| data = readl(base + GPIO_DS0); |
| data = (data >> pin) & 1; |
| *config = data; |
| data = readl(base + GPIO_DS1); |
| data = (data >> pin) & 1; |
| *config |= (data << 1); |
| break; |
| case PINCFG_TYPE_SKEW_RATE: |
| data = readl(base + GPIO_SR); |
| *config = (data >> pin) & 1; |
| break; |
| case PINCFG_TYPE_INPUT_TYPE: |
| data = readl(base + GPIO_TYPE); |
| *config = (data >> pin) & 1; |
| break; |
| default: |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* set the pin config settings for a specified pin group */ |
| static int exynos5440_pinconf_group_set(struct pinctrl_dev *pctldev, |
| unsigned group, unsigned long *configs, |
| unsigned num_configs) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| const unsigned int *pins; |
| unsigned int cnt; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| pins = priv->pin_groups[group].pins; |
| |
| for (cnt = 0; cnt < priv->pin_groups[group].num_pins; cnt++) |
| exynos5440_pinconf_set(pctldev, pins[cnt], configs, |
| num_configs); |
| |
| return 0; |
| } |
| |
| /* get the pin config settings for a specified pin group */ |
| static int exynos5440_pinconf_group_get(struct pinctrl_dev *pctldev, |
| unsigned int group, unsigned long *config) |
| { |
| struct exynos5440_pinctrl_priv_data *priv; |
| const unsigned int *pins; |
| |
| priv = pinctrl_dev_get_drvdata(pctldev); |
| pins = priv->pin_groups[group].pins; |
| exynos5440_pinconf_get(pctldev, pins[0], config); |
| return 0; |
| } |
| |
| /* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */ |
| static const struct pinconf_ops exynos5440_pinconf_ops = { |
| .pin_config_get = exynos5440_pinconf_get, |
| .pin_config_set = exynos5440_pinconf_set, |
| .pin_config_group_get = exynos5440_pinconf_group_get, |
| .pin_config_group_set = exynos5440_pinconf_group_set, |
| }; |
| |
| /* gpiolib gpio_set callback function */ |
| static void exynos5440_gpio_set(struct gpio_chip *gc, unsigned offset, int value) |
| { |
| struct exynos5440_pinctrl_priv_data *priv = gpiochip_get_data(gc); |
| void __iomem *base = priv->reg_base; |
| u32 data; |
| |
| data = readl(base + GPIO_VAL); |
| data &= ~(1 << offset); |
| if (value) |
| data |= 1 << offset; |
| writel(data, base + GPIO_VAL); |
| } |
| |
| /* gpiolib gpio_get callback function */ |
| static int exynos5440_gpio_get(struct gpio_chip *gc, unsigned offset) |
| { |
| struct exynos5440_pinctrl_priv_data *priv = gpiochip_get_data(gc); |
| void __iomem *base = priv->reg_base; |
| u32 data; |
| |
| data = readl(base + GPIO_IN); |
| data >>= offset; |
| data &= 1; |
| return data; |
| } |
| |
| /* gpiolib gpio_direction_input callback function */ |
| static int exynos5440_gpio_direction_input(struct gpio_chip *gc, unsigned offset) |
| { |
| struct exynos5440_pinctrl_priv_data *priv = gpiochip_get_data(gc); |
| void __iomem *base = priv->reg_base; |
| u32 data; |
| |
| /* first disable the data output enable on this pin */ |
| data = readl(base + GPIO_OE); |
| data &= ~(1 << offset); |
| writel(data, base + GPIO_OE); |
| |
| /* now enable input on this pin */ |
| data = readl(base + GPIO_IE); |
| data |= 1 << offset; |
| writel(data, base + GPIO_IE); |
| return 0; |
| } |
| |
| /* gpiolib gpio_direction_output callback function */ |
| static int exynos5440_gpio_direction_output(struct gpio_chip *gc, unsigned offset, |
| int value) |
| { |
| struct exynos5440_pinctrl_priv_data *priv = gpiochip_get_data(gc); |
| void __iomem *base = priv->reg_base; |
| u32 data; |
| |
| exynos5440_gpio_set(gc, offset, value); |
| |
| /* first disable the data input enable on this pin */ |
| data = readl(base + GPIO_IE); |
| data &= ~(1 << offset); |
| writel(data, base + GPIO_IE); |
| |
| /* now enable output on this pin */ |
| data = readl(base + GPIO_OE); |
| data |= 1 << offset; |
| writel(data, base + GPIO_OE); |
| return 0; |
| } |
| |
| /* gpiolib gpio_to_irq callback function */ |
| static int exynos5440_gpio_to_irq(struct gpio_chip *gc, unsigned offset) |
| { |
| struct exynos5440_pinctrl_priv_data *priv = gpiochip_get_data(gc); |
| unsigned int virq; |
| |
| if (offset < 16 || offset > 23) |
| return -ENXIO; |
| |
| if (!priv->irq_domain) |
| return -ENXIO; |
| |
| virq = irq_create_mapping(priv->irq_domain, offset - 16); |
| return virq ? : -ENXIO; |
| } |
| |
| /* parse the pin numbers listed in the 'samsung,exynos5440-pins' property */ |
| static int exynos5440_pinctrl_parse_dt_pins(struct platform_device *pdev, |
| struct device_node *cfg_np, unsigned int **pin_list, |
| unsigned int *npins) |
| { |
| struct device *dev = &pdev->dev; |
| struct property *prop; |
| |
| prop = of_find_property(cfg_np, "samsung,exynos5440-pins", NULL); |
| if (!prop) |
| return -ENOENT; |
| |
| *npins = prop->length / sizeof(unsigned long); |
| if (!*npins) { |
| dev_err(dev, "invalid pin list in %s node", cfg_np->name); |
| return -EINVAL; |
| } |
| |
| *pin_list = devm_kzalloc(dev, *npins * sizeof(**pin_list), GFP_KERNEL); |
| if (!*pin_list) |
| return -ENOMEM; |
| |
| return of_property_read_u32_array(cfg_np, "samsung,exynos5440-pins", |
| *pin_list, *npins); |
| } |
| |
| /* |
| * Parse the information about all the available pin groups and pin functions |
| * from device node of the pin-controller. |
| */ |
| static int exynos5440_pinctrl_parse_dt(struct platform_device *pdev, |
| struct exynos5440_pinctrl_priv_data *priv) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *dev_np = dev->of_node; |
| struct device_node *cfg_np; |
| struct exynos5440_pin_group *groups, *grp; |
| struct exynos5440_pmx_func *functions, *func; |
| unsigned *pin_list; |
| unsigned int npins, grp_cnt, func_idx = 0; |
| char *gname, *fname; |
| int ret; |
| |
| grp_cnt = of_get_child_count(dev_np); |
| if (!grp_cnt) |
| return -EINVAL; |
| |
| groups = devm_kzalloc(dev, grp_cnt * sizeof(*groups), GFP_KERNEL); |
| if (!groups) |
| return -EINVAL; |
| |
| grp = groups; |
| |
| functions = devm_kzalloc(dev, grp_cnt * sizeof(*functions), GFP_KERNEL); |
| if (!functions) |
| return -EINVAL; |
| |
| func = functions; |
| |
| /* |
| * Iterate over all the child nodes of the pin controller node |
| * and create pin groups and pin function lists. |
| */ |
| for_each_child_of_node(dev_np, cfg_np) { |
| u32 function; |
| |
| ret = exynos5440_pinctrl_parse_dt_pins(pdev, cfg_np, |
| &pin_list, &npins); |
| if (ret) { |
| gname = NULL; |
| goto skip_to_pin_function; |
| } |
| |
| /* derive pin group name from the node name */ |
| gname = devm_kasprintf(dev, GFP_KERNEL, |
| "%s%s", cfg_np->name, GROUP_SUFFIX); |
| if (!gname) |
| return -ENOMEM; |
| |
| grp->name = gname; |
| grp->pins = pin_list; |
| grp->num_pins = npins; |
| grp++; |
| |
| skip_to_pin_function: |
| ret = of_property_read_u32(cfg_np, "samsung,exynos5440-pin-function", |
| &function); |
| if (ret) |
| continue; |
| |
| /* derive function name from the node name */ |
| fname = devm_kasprintf(dev, GFP_KERNEL, |
| "%s%s", cfg_np->name, FUNCTION_SUFFIX); |
| if (!fname) |
| return -ENOMEM; |
| |
| func->name = fname; |
| func->groups = devm_kzalloc(dev, sizeof(char *), GFP_KERNEL); |
| if (!func->groups) |
| return -ENOMEM; |
| func->groups[0] = gname; |
| func->num_groups = gname ? 1 : 0; |
| func->function = function; |
| func++; |
| func_idx++; |
| } |
| |
| priv->pin_groups = groups; |
| priv->nr_groups = grp_cnt; |
| priv->pmx_functions = functions; |
| priv->nr_functions = func_idx; |
| return 0; |
| } |
| |
| /* register the pinctrl interface with the pinctrl subsystem */ |
| static int exynos5440_pinctrl_register(struct platform_device *pdev, |
| struct exynos5440_pinctrl_priv_data *priv) |
| { |
| struct device *dev = &pdev->dev; |
| struct pinctrl_desc *ctrldesc; |
| struct pinctrl_dev *pctl_dev; |
| struct pinctrl_pin_desc *pindesc, *pdesc; |
| char *pin_names; |
| int pin, ret; |
| |
| ctrldesc = devm_kzalloc(dev, sizeof(*ctrldesc), GFP_KERNEL); |
| if (!ctrldesc) |
| return -ENOMEM; |
| |
| ctrldesc->name = "exynos5440-pinctrl"; |
| ctrldesc->owner = THIS_MODULE; |
| ctrldesc->pctlops = &exynos5440_pctrl_ops; |
| ctrldesc->pmxops = &exynos5440_pinmux_ops; |
| ctrldesc->confops = &exynos5440_pinconf_ops; |
| |
| pindesc = devm_kzalloc(&pdev->dev, sizeof(*pindesc) * |
| EXYNOS5440_MAX_PINS, GFP_KERNEL); |
| if (!pindesc) |
| return -ENOMEM; |
| ctrldesc->pins = pindesc; |
| ctrldesc->npins = EXYNOS5440_MAX_PINS; |
| |
| /* dynamically populate the pin number and pin name for pindesc */ |
| for (pin = 0, pdesc = pindesc; pin < ctrldesc->npins; pin++, pdesc++) |
| pdesc->number = pin; |
| |
| /* |
| * allocate space for storing the dynamically generated names for all |
| * the pins which belong to this pin-controller. |
| */ |
| pin_names = devm_kzalloc(&pdev->dev, sizeof(char) * PIN_NAME_LENGTH * |
| ctrldesc->npins, GFP_KERNEL); |
| if (!pin_names) |
| return -ENOMEM; |
| |
| /* for each pin, set the name of the pin */ |
| for (pin = 0; pin < ctrldesc->npins; pin++) { |
| snprintf(pin_names, 6, "gpio%02d", pin); |
| pdesc = pindesc + pin; |
| pdesc->name = pin_names; |
| pin_names += PIN_NAME_LENGTH; |
| } |
| |
| ret = exynos5440_pinctrl_parse_dt(pdev, priv); |
| if (ret) |
| return ret; |
| |
| pctl_dev = devm_pinctrl_register(&pdev->dev, ctrldesc, priv); |
| if (IS_ERR(pctl_dev)) { |
| dev_err(&pdev->dev, "could not register pinctrl driver\n"); |
| return PTR_ERR(pctl_dev); |
| } |
| |
| priv->range.name = "exynos5440-pctrl-gpio-range"; |
| priv->range.id = 0; |
| priv->range.base = 0; |
| priv->range.npins = EXYNOS5440_MAX_PINS; |
| priv->range.gc = priv->gc; |
| pinctrl_add_gpio_range(pctl_dev, &priv->range); |
| return 0; |
| } |
| |
| /* register the gpiolib interface with the gpiolib subsystem */ |
| static int exynos5440_gpiolib_register(struct platform_device *pdev, |
| struct exynos5440_pinctrl_priv_data *priv) |
| { |
| struct gpio_chip *gc; |
| int ret; |
| |
| gc = devm_kzalloc(&pdev->dev, sizeof(*gc), GFP_KERNEL); |
| if (!gc) |
| return -ENOMEM; |
| |
| priv->gc = gc; |
| gc->base = 0; |
| gc->ngpio = EXYNOS5440_MAX_PINS; |
| gc->parent = &pdev->dev; |
| gc->set = exynos5440_gpio_set; |
| gc->get = exynos5440_gpio_get; |
| gc->direction_input = exynos5440_gpio_direction_input; |
| gc->direction_output = exynos5440_gpio_direction_output; |
| gc->to_irq = exynos5440_gpio_to_irq; |
| gc->label = "gpiolib-exynos5440"; |
| gc->owner = THIS_MODULE; |
| ret = gpiochip_add_data(gc, priv); |
| if (ret) { |
| dev_err(&pdev->dev, "failed to register gpio_chip %s, error " |
| "code: %d\n", gc->label, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* unregister the gpiolib interface with the gpiolib subsystem */ |
| static int exynos5440_gpiolib_unregister(struct platform_device *pdev, |
| struct exynos5440_pinctrl_priv_data *priv) |
| { |
| gpiochip_remove(priv->gc); |
| return 0; |
| } |
| |
| static void exynos5440_gpio_irq_unmask(struct irq_data *irqd) |
| { |
| struct exynos5440_pinctrl_priv_data *d; |
| unsigned long gpio_int; |
| |
| d = irq_data_get_irq_chip_data(irqd); |
| gpio_int = readl(d->reg_base + GPIO_INT); |
| gpio_int |= 1 << irqd->hwirq; |
| writel(gpio_int, d->reg_base + GPIO_INT); |
| } |
| |
| static void exynos5440_gpio_irq_mask(struct irq_data *irqd) |
| { |
| struct exynos5440_pinctrl_priv_data *d; |
| unsigned long gpio_int; |
| |
| d = irq_data_get_irq_chip_data(irqd); |
| gpio_int = readl(d->reg_base + GPIO_INT); |
| gpio_int &= ~(1 << irqd->hwirq); |
| writel(gpio_int, d->reg_base + GPIO_INT); |
| } |
| |
| /* irq_chip for gpio interrupts */ |
| static struct irq_chip exynos5440_gpio_irq_chip = { |
| .name = "exynos5440_gpio_irq_chip", |
| .irq_unmask = exynos5440_gpio_irq_unmask, |
| .irq_mask = exynos5440_gpio_irq_mask, |
| }; |
| |
| /* interrupt handler for GPIO interrupts 0..7 */ |
| static irqreturn_t exynos5440_gpio_irq(int irq, void *data) |
| { |
| struct exynos5440_gpio_intr_data *intd = data; |
| struct exynos5440_pinctrl_priv_data *d = intd->priv; |
| int virq; |
| |
| virq = irq_linear_revmap(d->irq_domain, intd->gpio_int); |
| if (!virq) |
| return IRQ_NONE; |
| generic_handle_irq(virq); |
| return IRQ_HANDLED; |
| } |
| |
| static int exynos5440_gpio_irq_map(struct irq_domain *h, unsigned int virq, |
| irq_hw_number_t hw) |
| { |
| struct exynos5440_pinctrl_priv_data *d = h->host_data; |
| |
| irq_set_chip_data(virq, d); |
| irq_set_chip_and_handler(virq, &exynos5440_gpio_irq_chip, |
| handle_level_irq); |
| return 0; |
| } |
| |
| /* irq domain callbacks for gpio interrupt controller */ |
| static const struct irq_domain_ops exynos5440_gpio_irqd_ops = { |
| .map = exynos5440_gpio_irq_map, |
| .xlate = irq_domain_xlate_twocell, |
| }; |
| |
| /* setup handling of gpio interrupts */ |
| static int exynos5440_gpio_irq_init(struct platform_device *pdev, |
| struct exynos5440_pinctrl_priv_data *priv) |
| { |
| struct device *dev = &pdev->dev; |
| struct exynos5440_gpio_intr_data *intd; |
| int i, irq, ret; |
| |
| intd = devm_kzalloc(dev, sizeof(*intd) * EXYNOS5440_MAX_GPIO_INT, |
| GFP_KERNEL); |
| if (!intd) |
| return -ENOMEM; |
| |
| for (i = 0; i < EXYNOS5440_MAX_GPIO_INT; i++) { |
| irq = irq_of_parse_and_map(dev->of_node, i); |
| if (irq <= 0) { |
| dev_err(dev, "irq parsing failed\n"); |
| return -EINVAL; |
| } |
| |
| intd->gpio_int = i; |
| intd->priv = priv; |
| ret = devm_request_irq(dev, irq, exynos5440_gpio_irq, |
| 0, dev_name(dev), intd++); |
| if (ret) { |
| dev_err(dev, "irq request failed\n"); |
| return -ENXIO; |
| } |
| } |
| |
| priv->irq_domain = irq_domain_add_linear(dev->of_node, |
| EXYNOS5440_MAX_GPIO_INT, |
| &exynos5440_gpio_irqd_ops, priv); |
| if (!priv->irq_domain) { |
| dev_err(dev, "failed to create irq domain\n"); |
| return -ENXIO; |
| } |
| |
| return 0; |
| } |
| |
| static int exynos5440_pinctrl_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct exynos5440_pinctrl_priv_data *priv; |
| struct resource *res; |
| int ret; |
| |
| if (!dev->of_node) { |
| dev_err(dev, "device tree node not found\n"); |
| return -ENODEV; |
| } |
| |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| priv->reg_base = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(priv->reg_base)) |
| return PTR_ERR(priv->reg_base); |
| |
| ret = exynos5440_gpiolib_register(pdev, priv); |
| if (ret) |
| return ret; |
| |
| ret = exynos5440_pinctrl_register(pdev, priv); |
| if (ret) { |
| exynos5440_gpiolib_unregister(pdev, priv); |
| return ret; |
| } |
| |
| ret = exynos5440_gpio_irq_init(pdev, priv); |
| if (ret) { |
| dev_err(dev, "failed to setup gpio interrupts\n"); |
| return ret; |
| } |
| |
| platform_set_drvdata(pdev, priv); |
| dev_info(dev, "EXYNOS5440 pinctrl driver registered\n"); |
| return 0; |
| } |
| |
| static const struct of_device_id exynos5440_pinctrl_dt_match[] = { |
| { .compatible = "samsung,exynos5440-pinctrl" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, exynos5440_pinctrl_dt_match); |
| |
| static struct platform_driver exynos5440_pinctrl_driver = { |
| .probe = exynos5440_pinctrl_probe, |
| .driver = { |
| .name = "exynos5440-pinctrl", |
| .of_match_table = exynos5440_pinctrl_dt_match, |
| .suppress_bind_attrs = true, |
| }, |
| }; |
| |
| static int __init exynos5440_pinctrl_drv_register(void) |
| { |
| return platform_driver_register(&exynos5440_pinctrl_driver); |
| } |
| postcore_initcall(exynos5440_pinctrl_drv_register); |
| |
| static void __exit exynos5440_pinctrl_drv_unregister(void) |
| { |
| platform_driver_unregister(&exynos5440_pinctrl_driver); |
| } |
| module_exit(exynos5440_pinctrl_drv_unregister); |
| |
| MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com>"); |
| MODULE_DESCRIPTION("Samsung EXYNOS5440 SoC pinctrl driver"); |
| MODULE_LICENSE("GPL v2"); |