diff options
Diffstat (limited to 'drivers/pinctrl/pinctrl-samsung.c')
| -rw-r--r-- | drivers/pinctrl/pinctrl-samsung.c | 551 |
1 files changed, 422 insertions, 129 deletions
diff --git a/drivers/pinctrl/pinctrl-samsung.c b/drivers/pinctrl/pinctrl-samsung.c index dd108a94acf..3e61d0f8f14 100644 --- a/drivers/pinctrl/pinctrl-samsung.c +++ b/drivers/pinctrl/pinctrl-samsung.c @@ -26,6 +26,9 @@ #include <linux/slab.h> #include <linux/err.h> #include <linux/gpio.h> +#include <linux/irqdomain.h> +#include <linux/spinlock.h> +#include <linux/syscore_ops.h> #include "core.h" #include "pinctrl-samsung.h" @@ -36,7 +39,7 @@ #define FSUFFIX_LEN sizeof(FUNCTION_SUFFIX) /* list of all possible config options supported */ -struct pin_config { +static struct pin_config { char *prop_cfg; unsigned int cfg_type; } pcfgs[] = { @@ -46,6 +49,16 @@ struct pin_config { { "samsung,pin-pud-pdn", PINCFG_TYPE_PUD_PDN }, }; +/* Global list of devices (struct samsung_pinctrl_drv_data) */ +static LIST_HEAD(drvdata_list); + +static unsigned int pin_base; + +static inline struct samsung_pin_bank *gc_to_pin_bank(struct gpio_chip *gc) +{ + return container_of(gc, struct samsung_pin_bank, gpio_chip); +} + /* check if the selector is a valid pin group selector */ static int samsung_get_group_count(struct pinctrl_dev *pctldev) { @@ -206,7 +219,7 @@ static void samsung_dt_free_map(struct pinctrl_dev *pctldev, } /* list of pinctrl callbacks for the pinctrl core */ -static struct pinctrl_ops samsung_pctrl_ops = { +static const struct pinctrl_ops samsung_pctrl_ops = { .get_groups_count = samsung_get_group_count, .get_group_name = samsung_get_group_name, .get_group_pins = samsung_get_group_pins, @@ -250,14 +263,12 @@ static int samsung_pinmux_get_groups(struct pinctrl_dev *pctldev, * given a pin number that is local to a pin controller, find out the pin bank * and the register base of the pin bank. */ -static void pin_to_reg_bank(struct gpio_chip *gc, unsigned pin, - void __iomem **reg, u32 *offset, +static void pin_to_reg_bank(struct samsung_pinctrl_drv_data *drvdata, + unsigned pin, void __iomem **reg, u32 *offset, struct samsung_pin_bank **bank) { - struct samsung_pinctrl_drv_data *drvdata; struct samsung_pin_bank *b; - drvdata = dev_get_drvdata(gc->dev); b = drvdata->ctrl->pin_banks; while ((pin >= b->pin_base) && @@ -268,10 +279,6 @@ static void pin_to_reg_bank(struct gpio_chip *gc, unsigned pin, *offset = pin - b->pin_base; if (bank) *bank = b; - - /* some banks have two config registers in a single bank */ - if (*offset * b->func_width > BITS_PER_LONG) - *reg += 4; } /* enable or disable a pinmux function */ @@ -283,6 +290,7 @@ static void samsung_pinmux_setup(struct pinctrl_dev *pctldev, unsigned selector, struct samsung_pin_bank *bank; void __iomem *reg; u32 mask, shift, data, pin_offset, cnt; + unsigned long flags; drvdata = pinctrl_dev_get_drvdata(pctldev); pins = drvdata->pin_groups[group].pins; @@ -292,16 +300,28 @@ static void samsung_pinmux_setup(struct pinctrl_dev *pctldev, unsigned selector, * pin function number in the config register. */ for (cnt = 0; cnt < drvdata->pin_groups[group].num_pins; cnt++) { - pin_to_reg_bank(drvdata->gc, pins[cnt] - drvdata->ctrl->base, + struct samsung_pin_bank_type *type; + + pin_to_reg_bank(drvdata, pins[cnt] - drvdata->ctrl->base, ®, &pin_offset, &bank); - mask = (1 << bank->func_width) - 1; - shift = pin_offset * bank->func_width; + type = bank->type; + mask = (1 << type->fld_width[PINCFG_TYPE_FUNC]) - 1; + shift = pin_offset * type->fld_width[PINCFG_TYPE_FUNC]; + if (shift >= 32) { + /* Some banks have two config registers */ + shift -= 32; + reg += 4; + } + + spin_lock_irqsave(&bank->slock, flags); - data = readl(reg); + data = readl(reg + type->reg_offset[PINCFG_TYPE_FUNC]); data &= ~(mask << shift); if (enable) data |= drvdata->pin_groups[group].func << shift; - writel(data, reg); + writel(data, reg + type->reg_offset[PINCFG_TYPE_FUNC]); + + spin_unlock_irqrestore(&bank->slock, flags); } } @@ -328,24 +348,44 @@ static void samsung_pinmux_disable(struct pinctrl_dev *pctldev, static int samsung_pinmux_gpio_set_direction(struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset, bool input) { + struct samsung_pin_bank_type *type; struct samsung_pin_bank *bank; + struct samsung_pinctrl_drv_data *drvdata; void __iomem *reg; u32 data, pin_offset, mask, shift; + unsigned long flags; - pin_to_reg_bank(range->gc, offset, ®, &pin_offset, &bank); - mask = (1 << bank->func_width) - 1; - shift = pin_offset * bank->func_width; + bank = gc_to_pin_bank(range->gc); + type = bank->type; + drvdata = pinctrl_dev_get_drvdata(pctldev); + + pin_offset = offset - bank->pin_base; + reg = drvdata->virt_base + bank->pctl_offset + + type->reg_offset[PINCFG_TYPE_FUNC]; + + mask = (1 << type->fld_width[PINCFG_TYPE_FUNC]) - 1; + shift = pin_offset * type->fld_width[PINCFG_TYPE_FUNC]; + if (shift >= 32) { + /* Some banks have two config registers */ + shift -= 32; + reg += 4; + } + + spin_lock_irqsave(&bank->slock, flags); data = readl(reg); data &= ~(mask << shift); if (!input) data |= FUNC_OUTPUT << shift; writel(data, reg); + + spin_unlock_irqrestore(&bank->slock, flags); + return 0; } /* list of pinmux callbacks for the pinmux vertical in pinctrl core */ -static struct pinmux_ops samsung_pinmux_ops = { +static const struct pinmux_ops samsung_pinmux_ops = { .get_functions_count = samsung_get_functions_count, .get_function_name = samsung_pinmux_get_fname, .get_function_groups = samsung_pinmux_get_groups, @@ -359,37 +399,26 @@ static int samsung_pinconf_rw(struct pinctrl_dev *pctldev, unsigned int pin, unsigned long *config, bool set) { struct samsung_pinctrl_drv_data *drvdata; + struct samsung_pin_bank_type *type; struct samsung_pin_bank *bank; void __iomem *reg_base; enum pincfg_type cfg_type = PINCFG_UNPACK_TYPE(*config); u32 data, width, pin_offset, mask, shift; u32 cfg_value, cfg_reg; + unsigned long flags; drvdata = pinctrl_dev_get_drvdata(pctldev); - pin_to_reg_bank(drvdata->gc, pin - drvdata->ctrl->base, ®_base, + pin_to_reg_bank(drvdata, pin - drvdata->ctrl->base, ®_base, &pin_offset, &bank); + type = bank->type; - switch (cfg_type) { - case PINCFG_TYPE_PUD: - width = bank->pud_width; - cfg_reg = PUD_REG; - break; - case PINCFG_TYPE_DRV: - width = bank->drv_width; - cfg_reg = DRV_REG; - break; - case PINCFG_TYPE_CON_PDN: - width = bank->conpdn_width; - cfg_reg = CONPDN_REG; - break; - case PINCFG_TYPE_PUD_PDN: - width = bank->pudpdn_width; - cfg_reg = PUDPDN_REG; - break; - default: - WARN_ON(1); + if (cfg_type >= PINCFG_TYPE_NUM || !type->fld_width[cfg_type]) return -EINVAL; - } + + width = type->fld_width[cfg_type]; + cfg_reg = type->reg_offset[cfg_type]; + + spin_lock_irqsave(&bank->slock, flags); mask = (1 << width) - 1; shift = pin_offset * width; @@ -405,14 +434,25 @@ static int samsung_pinconf_rw(struct pinctrl_dev *pctldev, unsigned int pin, data &= mask; *config = PINCFG_PACK(cfg_type, data); } + + spin_unlock_irqrestore(&bank->slock, flags); + return 0; } /* set the pin config settings for a specified pin */ static int samsung_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin, - unsigned long config) + unsigned long *configs, unsigned num_configs) { - return samsung_pinconf_rw(pctldev, pin, &config, true); + int i, ret; + + for (i = 0; i < num_configs; i++) { + ret = samsung_pinconf_rw(pctldev, pin, &configs[i], true); + if (ret < 0) + return ret; + } /* for each config */ + + return 0; } /* get the pin config settings for a specified pin */ @@ -424,7 +464,8 @@ static int samsung_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin, /* set the pin config settings for a specified pin group */ static int samsung_pinconf_group_set(struct pinctrl_dev *pctldev, - unsigned group, unsigned long config) + unsigned group, unsigned long *configs, + unsigned num_configs) { struct samsung_pinctrl_drv_data *drvdata; const unsigned int *pins; @@ -434,7 +475,7 @@ static int samsung_pinconf_group_set(struct pinctrl_dev *pctldev, pins = drvdata->pin_groups[group].pins; for (cnt = 0; cnt < drvdata->pin_groups[group].num_pins; cnt++) - samsung_pinconf_set(pctldev, pins[cnt], config); + samsung_pinconf_set(pctldev, pins[cnt], configs, num_configs); return 0; } @@ -453,7 +494,7 @@ static int samsung_pinconf_group_get(struct pinctrl_dev *pctldev, } /* list of pinconfig callbacks for pinconfig vertical in the pinctrl code */ -static struct pinconf_ops samsung_pinconf_ops = { +static const struct pinconf_ops samsung_pinconf_ops = { .pin_config_get = samsung_pinconf_get, .pin_config_set = samsung_pinconf_set, .pin_config_group_get = samsung_pinconf_group_get, @@ -463,26 +504,37 @@ static struct pinconf_ops samsung_pinconf_ops = { /* gpiolib gpio_set callback function */ static void samsung_gpio_set(struct gpio_chip *gc, unsigned offset, int value) { + struct samsung_pin_bank *bank = gc_to_pin_bank(gc); + struct samsung_pin_bank_type *type = bank->type; + unsigned long flags; void __iomem *reg; - u32 pin_offset, data; + u32 data; - pin_to_reg_bank(gc, offset, ®, &pin_offset, NULL); - data = readl(reg + DAT_REG); - data &= ~(1 << pin_offset); + reg = bank->drvdata->virt_base + bank->pctl_offset; + + spin_lock_irqsave(&bank->slock, flags); + + data = readl(reg + type->reg_offset[PINCFG_TYPE_DAT]); + data &= ~(1 << offset); if (value) - data |= 1 << pin_offset; - writel(data, reg + DAT_REG); + data |= 1 << offset; + writel(data, reg + type->reg_offset[PINCFG_TYPE_DAT]); + + spin_unlock_irqrestore(&bank->slock, flags); } /* gpiolib gpio_get callback function */ static int samsung_gpio_get(struct gpio_chip *gc, unsigned offset) { void __iomem *reg; - u32 pin_offset, data; + u32 data; + struct samsung_pin_bank *bank = gc_to_pin_bank(gc); + struct samsung_pin_bank_type *type = bank->type; - pin_to_reg_bank(gc, offset, ®, &pin_offset, NULL); - data = readl(reg + DAT_REG); - data >>= pin_offset; + reg = bank->drvdata->virt_base + bank->pctl_offset; + + data = readl(reg + type->reg_offset[PINCFG_TYPE_DAT]); + data >>= offset; data &= 1; return data; } @@ -510,12 +562,31 @@ static int samsung_gpio_direction_output(struct gpio_chip *gc, unsigned offset, } /* + * gpiolib gpio_to_irq callback function. Creates a mapping between a GPIO pin + * and a virtual IRQ, if not already present. + */ +static int samsung_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct samsung_pin_bank *bank = gc_to_pin_bank(gc); + unsigned int virq; + + if (!bank->irq_domain) + return -ENXIO; + + virq = irq_create_mapping(bank->irq_domain, offset); + + return (virq) ? : -ENXIO; +} + +/* * Parse the pin names listed in the 'samsung,pins' property and convert it * into a list of gpio numbers are create a pin group from it. */ -static int __init samsung_pinctrl_parse_dt_pins(struct platform_device *pdev, - struct device_node *cfg_np, struct pinctrl_desc *pctl, - unsigned int **pin_list, unsigned int *npins) +static int samsung_pinctrl_parse_dt_pins(struct platform_device *pdev, + struct device_node *cfg_np, + struct pinctrl_desc *pctl, + unsigned int **pin_list, + unsigned int *npins) { struct device *dev = &pdev->dev; struct property *prop; @@ -524,7 +595,7 @@ static int __init samsung_pinctrl_parse_dt_pins(struct platform_device *pdev, const char *pin_name; *npins = of_property_count_strings(cfg_np, "samsung,pins"); - if (*npins < 0) { + if (IS_ERR_VALUE(*npins)) { dev_err(dev, "invalid pin list in %s node", cfg_np->name); return -EINVAL; } @@ -560,8 +631,8 @@ static int __init samsung_pinctrl_parse_dt_pins(struct platform_device *pdev, * from device node of the pin-controller. A pin group is formed with all * the pins listed in the "samsung,pins" property. */ -static int __init samsung_pinctrl_parse_dt(struct platform_device *pdev, - struct samsung_pinctrl_drv_data *drvdata) +static int samsung_pinctrl_parse_dt(struct platform_device *pdev, + struct samsung_pinctrl_drv_data *drvdata) { struct device *dev = &pdev->dev; struct device_node *dev_np = dev->of_node; @@ -597,7 +668,7 @@ static int __init samsung_pinctrl_parse_dt(struct platform_device *pdev, */ for_each_child_of_node(dev_np, cfg_np) { u32 function; - if (of_find_property(cfg_np, "interrupt-controller", NULL)) + if (!of_find_property(cfg_np, "samsung,pins", NULL)) continue; ret = samsung_pinctrl_parse_dt_pins(pdev, cfg_np, @@ -655,8 +726,8 @@ static int __init samsung_pinctrl_parse_dt(struct platform_device *pdev, } /* register the pinctrl interface with the pinctrl subsystem */ -static int __init samsung_pinctrl_register(struct platform_device *pdev, - struct samsung_pinctrl_drv_data *drvdata) +static int samsung_pinctrl_register(struct platform_device *pdev, + struct samsung_pinctrl_drv_data *drvdata) { struct pinctrl_desc *ctrldesc = &drvdata->pctl; struct pinctrl_pin_desc *pindesc, *pdesc; @@ -678,7 +749,6 @@ static int __init samsung_pinctrl_register(struct platform_device *pdev, } ctrldesc->pins = pindesc; ctrldesc->npins = drvdata->ctrl->nr_pins; - ctrldesc->npins = drvdata->ctrl->nr_pins; /* dynamically populate the pin number and pin name for pindesc */ for (pin = 0, pdesc = pindesc; pin < ctrldesc->npins; pin++, pdesc++) @@ -706,93 +776,145 @@ static int __init samsung_pinctrl_register(struct platform_device *pdev, } } + ret = samsung_pinctrl_parse_dt(pdev, drvdata); + if (ret) + return ret; + drvdata->pctl_dev = pinctrl_register(ctrldesc, &pdev->dev, drvdata); if (!drvdata->pctl_dev) { dev_err(&pdev->dev, "could not register pinctrl driver\n"); return -EINVAL; } - drvdata->grange.name = "samsung-pctrl-gpio-range"; - drvdata->grange.id = 0; - drvdata->grange.base = drvdata->ctrl->base; - drvdata->grange.npins = drvdata->ctrl->nr_pins; - drvdata->grange.gc = drvdata->gc; - pinctrl_add_gpio_range(drvdata->pctl_dev, &drvdata->grange); - - ret = samsung_pinctrl_parse_dt(pdev, drvdata); - if (ret) { - pinctrl_unregister(drvdata->pctl_dev); - return ret; + for (bank = 0; bank < drvdata->ctrl->nr_banks; ++bank) { + pin_bank = &drvdata->ctrl->pin_banks[bank]; + pin_bank->grange.name = pin_bank->name; + pin_bank->grange.id = bank; + pin_bank->grange.pin_base = pin_bank->pin_base; + pin_bank->grange.base = pin_bank->gpio_chip.base; + pin_bank->grange.npins = pin_bank->gpio_chip.ngpio; + pin_bank->grange.gc = &pin_bank->gpio_chip; + pinctrl_add_gpio_range(drvdata->pctl_dev, &pin_bank->grange); } return 0; } +static const struct gpio_chip samsung_gpiolib_chip = { + .set = samsung_gpio_set, + .get = samsung_gpio_get, + .direction_input = samsung_gpio_direction_input, + .direction_output = samsung_gpio_direction_output, + .to_irq = samsung_gpio_to_irq, + .owner = THIS_MODULE, +}; + /* register the gpiolib interface with the gpiolib subsystem */ -static int __init samsung_gpiolib_register(struct platform_device *pdev, - struct samsung_pinctrl_drv_data *drvdata) +static int samsung_gpiolib_register(struct platform_device *pdev, + struct samsung_pinctrl_drv_data *drvdata) { + struct samsung_pin_ctrl *ctrl = drvdata->ctrl; + struct samsung_pin_bank *bank = ctrl->pin_banks; struct gpio_chip *gc; int ret; - - gc = devm_kzalloc(&pdev->dev, sizeof(*gc), GFP_KERNEL); - if (!gc) { - dev_err(&pdev->dev, "mem alloc for gpio_chip failed\n"); - return -ENOMEM; - } - - drvdata->gc = gc; - gc->base = drvdata->ctrl->base; - gc->ngpio = drvdata->ctrl->nr_pins; - gc->dev = &pdev->dev; - gc->set = samsung_gpio_set; - gc->get = samsung_gpio_get; - gc->direction_input = samsung_gpio_direction_input; - gc->direction_output = samsung_gpio_direction_output; - gc->label = drvdata->ctrl->label; - gc->owner = THIS_MODULE; - ret = gpiochip_add(gc); - if (ret) { - dev_err(&pdev->dev, "failed to register gpio_chip %s, error " - "code: %d\n", gc->label, ret); - return ret; + int i; + + for (i = 0; i < ctrl->nr_banks; ++i, ++bank) { + bank->gpio_chip = samsung_gpiolib_chip; + + gc = &bank->gpio_chip; + gc->base = ctrl->base + bank->pin_base; + gc->ngpio = bank->nr_pins; + gc->dev = &pdev->dev; + gc->of_node = bank->of_node; + gc->label = bank->name; + + ret = gpiochip_add(gc); + if (ret) { + dev_err(&pdev->dev, "failed to register gpio_chip %s, error code: %d\n", + gc->label, ret); + goto fail; + } } return 0; + +fail: + for (--i, --bank; i >= 0; --i, --bank) + if (gpiochip_remove(&bank->gpio_chip)) + dev_err(&pdev->dev, "gpio chip %s remove failed\n", + bank->gpio_chip.label); + return ret; } /* unregister the gpiolib interface with the gpiolib subsystem */ -static int __init samsung_gpiolib_unregister(struct platform_device *pdev, - struct samsung_pinctrl_drv_data *drvdata) +static int samsung_gpiolib_unregister(struct platform_device *pdev, + struct samsung_pinctrl_drv_data *drvdata) { - int ret = gpiochip_remove(drvdata->gc); - if (ret) { + struct samsung_pin_ctrl *ctrl = drvdata->ctrl; + struct samsung_pin_bank *bank = ctrl->pin_banks; + int ret = 0; + int i; + + for (i = 0; !ret && i < ctrl->nr_banks; ++i, ++bank) + ret = gpiochip_remove(&bank->gpio_chip); + + if (ret) dev_err(&pdev->dev, "gpio chip remove failed\n"); - return ret; - } - return 0; + + return ret; } static const struct of_device_id samsung_pinctrl_dt_match[]; /* retrieve the soc specific data */ static struct samsung_pin_ctrl *samsung_pinctrl_get_soc_data( + struct samsung_pinctrl_drv_data *d, struct platform_device *pdev) { int id; const struct of_device_id *match; - const struct device_node *node = pdev->dev.of_node; + struct device_node *node = pdev->dev.of_node; + struct device_node *np; + struct samsung_pin_ctrl *ctrl; + struct samsung_pin_bank *bank; + int i; - id = of_alias_get_id(pdev->dev.of_node, "pinctrl"); + id = of_alias_get_id(node, "pinctrl"); if (id < 0) { dev_err(&pdev->dev, "failed to get alias id\n"); return NULL; } match = of_match_node(samsung_pinctrl_dt_match, node); - return (struct samsung_pin_ctrl *)match->data + id; + ctrl = (struct samsung_pin_ctrl *)match->data + id; + + bank = ctrl->pin_banks; + for (i = 0; i < ctrl->nr_banks; ++i, ++bank) { + spin_lock_init(&bank->slock); + bank->drvdata = d; + bank->pin_base = ctrl->nr_pins; + ctrl->nr_pins += bank->nr_pins; + } + + for_each_child_of_node(node, np) { + if (!of_find_property(np, "gpio-controller", NULL)) + continue; + bank = ctrl->pin_banks; + for (i = 0; i < ctrl->nr_banks; ++i, ++bank) { + if (!strcmp(bank->name, np->name)) { + bank->of_node = np; + break; + } + } + } + + ctrl->base = pin_base; + pin_base += ctrl->nr_pins; + + return ctrl; } -static int __devinit samsung_pinctrl_probe(struct platform_device *pdev) +static int samsung_pinctrl_probe(struct platform_device *pdev) { struct samsung_pinctrl_drv_data *drvdata; struct device *dev = &pdev->dev; @@ -805,32 +927,25 @@ static int __devinit samsung_pinctrl_probe(struct platform_device *pdev) return -ENODEV; } - ctrl = samsung_pinctrl_get_soc_data(pdev); - if (!ctrl) { - dev_err(&pdev->dev, "driver data not available\n"); - return -EINVAL; - } - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); if (!drvdata) { dev_err(dev, "failed to allocate memory for driver's " "private data\n"); return -ENOMEM; } + + ctrl = samsung_pinctrl_get_soc_data(drvdata, pdev); + if (!ctrl) { + dev_err(&pdev->dev, "driver data not available\n"); + return -EINVAL; + } drvdata->ctrl = ctrl; drvdata->dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "cannot find IO resource\n"); - return -ENOENT; - } - - drvdata->virt_base = devm_request_and_ioremap(&pdev->dev, res); - if (!drvdata->virt_base) { - dev_err(dev, "ioremap failed\n"); - return -ENODEV; - } + drvdata->virt_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drvdata->virt_base)) + return PTR_ERR(drvdata->virt_base); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res) @@ -852,12 +967,182 @@ static int __devinit samsung_pinctrl_probe(struct platform_device *pdev) ctrl->eint_wkup_init(drvdata); platform_set_drvdata(pdev, drvdata); + + /* Add to the global list */ + list_add_tail(&drvdata->node, &drvdata_list); + return 0; } +#ifdef CONFIG_PM + +/** + * samsung_pinctrl_suspend_dev - save pinctrl state for suspend for a device + * + * Save data for all banks handled by this device. + */ +static void samsung_pinctrl_suspend_dev( + struct samsung_pinctrl_drv_data *drvdata) +{ + struct samsung_pin_ctrl *ctrl = drvdata->ctrl; + void __iomem *virt_base = drvdata->virt_base; + int i; + + for (i = 0; i < ctrl->nr_banks; i++) { + struct samsung_pin_bank *bank = &ctrl->pin_banks[i]; + void __iomem *reg = virt_base + bank->pctl_offset; + + u8 *offs = bank->type->reg_offset; + u8 *widths = bank->type->fld_width; + enum pincfg_type type; + + /* Registers without a powerdown config aren't lost */ + if (!widths[PINCFG_TYPE_CON_PDN]) + continue; + + for (type = 0; type < PINCFG_TYPE_NUM; type++) + if (widths[type]) + bank->pm_save[type] = readl(reg + offs[type]); + + if (widths[PINCFG_TYPE_FUNC] * bank->nr_pins > 32) { + /* Some banks have two config registers */ + bank->pm_save[PINCFG_TYPE_NUM] = + readl(reg + offs[PINCFG_TYPE_FUNC] + 4); + pr_debug("Save %s @ %p (con %#010x %08x)\n", + bank->name, reg, + bank->pm_save[PINCFG_TYPE_FUNC], + bank->pm_save[PINCFG_TYPE_NUM]); + } else { + pr_debug("Save %s @ %p (con %#010x)\n", bank->name, + reg, bank->pm_save[PINCFG_TYPE_FUNC]); + } + } + + if (ctrl->suspend) + ctrl->suspend(drvdata); +} + +/** + * samsung_pinctrl_resume_dev - restore pinctrl state from suspend for a device + * + * Restore one of the banks that was saved during suspend. + * + * We don't bother doing anything complicated to avoid glitching lines since + * we're called before pad retention is turned off. + */ +static void samsung_pinctrl_resume_dev(struct samsung_pinctrl_drv_data *drvdata) +{ + struct samsung_pin_ctrl *ctrl = drvdata->ctrl; + void __iomem *virt_base = drvdata->virt_base; + int i; + + if (ctrl->resume) + ctrl->resume(drvdata); + + for (i = 0; i < ctrl->nr_banks; i++) { + struct samsung_pin_bank *bank = &ctrl->pin_banks[i]; + void __iomem *reg = virt_base + bank->pctl_offset; + + u8 *offs = bank->type->reg_offset; + u8 *widths = bank->type->fld_width; + enum pincfg_type type; + + /* Registers without a powerdown config aren't lost */ + if (!widths[PINCFG_TYPE_CON_PDN]) + continue; + + if (widths[PINCFG_TYPE_FUNC] * bank->nr_pins > 32) { + /* Some banks have two config registers */ + pr_debug("%s @ %p (con %#010x %08x => %#010x %08x)\n", + bank->name, reg, + readl(reg + offs[PINCFG_TYPE_FUNC]), + readl(reg + offs[PINCFG_TYPE_FUNC] + 4), + bank->pm_save[PINCFG_TYPE_FUNC], + bank->pm_save[PINCFG_TYPE_NUM]); + writel(bank->pm_save[PINCFG_TYPE_NUM], + reg + offs[PINCFG_TYPE_FUNC] + 4); + } else { + pr_debug("%s @ %p (con %#010x => %#010x)\n", bank->name, + reg, readl(reg + offs[PINCFG_TYPE_FUNC]), + bank->pm_save[PINCFG_TYPE_FUNC]); + } + for (type = 0; type < PINCFG_TYPE_NUM; type++) + if (widths[type]) + writel(bank->pm_save[type], reg + offs[type]); + } +} + +/** + * samsung_pinctrl_suspend - save pinctrl state for suspend + * + * Save data for all banks across all devices. + */ +static int samsung_pinctrl_suspend(void) +{ + struct samsung_pinctrl_drv_data *drvdata; + + list_for_each_entry(drvdata, &drvdata_list, node) { + samsung_pinctrl_suspend_dev(drvdata); + } + + return 0; +} + +/** + * samsung_pinctrl_resume - restore pinctrl state for suspend + * + * Restore data for all banks across all devices. + */ +static void samsung_pinctrl_resume(void) +{ + struct samsung_pinctrl_drv_data *drvdata; + + list_for_each_entry_reverse(drvdata, &drvdata_list, node) { + samsung_pinctrl_resume_dev(drvdata); + } +} + +#else +#define samsung_pinctrl_suspend NULL +#define samsung_pinctrl_resume NULL +#endif + +static struct syscore_ops samsung_pinctrl_syscore_ops = { + .suspend = samsung_pinctrl_suspend, + .resume = samsung_pinctrl_resume, +}; + static const struct of_device_id samsung_pinctrl_dt_match[] = { - { .compatible = "samsung,pinctrl-exynos4210", +#ifdef CONFIG_PINCTRL_EXYNOS + { .compatible = "samsung,exynos3250-pinctrl", + .data = (void *)exynos3250_pin_ctrl }, + { .compatible = "samsung,exynos4210-pinctrl", .data = (void *)exynos4210_pin_ctrl }, + { .compatible = "samsung,exynos4x12-pinctrl", + .data = (void *)exynos4x12_pin_ctrl }, + { .compatible = "samsung,exynos5250-pinctrl", + .data = (void *)exynos5250_pin_ctrl }, + { .compatible = "samsung,exynos5260-pinctrl", + .data = (void *)exynos5260_pin_ctrl }, + { .compatible = "samsung,exynos5420-pinctrl", + .data = (void *)exynos5420_pin_ctrl }, + { .compatible = "samsung,s5pv210-pinctrl", + .data = (void *)s5pv210_pin_ctrl }, +#endif +#ifdef CONFIG_PINCTRL_S3C64XX + { .compatible = "samsung,s3c64xx-pinctrl", + .data = s3c64xx_pin_ctrl }, +#endif +#ifdef CONFIG_PINCTRL_S3C24XX + { .compatible = "samsung,s3c2412-pinctrl", + .data = s3c2412_pin_ctrl }, + { .compatible = "samsung,s3c2416-pinctrl", + .data = s3c2416_pin_ctrl }, + { .compatible = "samsung,s3c2440-pinctrl", + .data = s3c2440_pin_ctrl }, + { .compatible = "samsung,s3c2450-pinctrl", + .data = s3c2450_pin_ctrl }, +#endif {}, }; MODULE_DEVICE_TABLE(of, samsung_pinctrl_dt_match); @@ -867,12 +1152,20 @@ static struct platform_driver samsung_pinctrl_driver = { .driver = { .name = "samsung-pinctrl", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(samsung_pinctrl_dt_match), + .of_match_table = samsung_pinctrl_dt_match, }, }; static int __init samsung_pinctrl_drv_register(void) { + /* + * Register syscore ops for save/restore of registers across suspend. + * It's important to ensure that this driver is running at an earlier + * initcall level than any arch-specific init calls that install syscore + * ops that turn off pad retention (like exynos_pm_resume). + */ + register_syscore_ops(&samsung_pinctrl_syscore_ops); + return platform_driver_register(&samsung_pinctrl_driver); } postcore_initcall(samsung_pinctrl_drv_register); |
