| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Sophgo CV18XX SoCs pinctrl driver. |
| * |
| * Copyright (C) 2024 Inochi Amaoto <inochiama@outlook.com> |
| * |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/export.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/bsearch.h> |
| #include <linux/seq_file.h> |
| #include <linux/spinlock.h> |
| |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/pinctrl/machine.h> |
| #include <linux/pinctrl/pinconf-generic.h> |
| #include <linux/pinctrl/pinconf.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/pinctrl/pinmux.h> |
| |
| #include <dt-bindings/pinctrl/pinctrl-cv18xx.h> |
| |
| #include "../core.h" |
| #include "../pinctrl-utils.h" |
| #include "../pinconf.h" |
| #include "../pinmux.h" |
| #include "pinctrl-cv18xx.h" |
| |
| struct cv1800_pinctrl { |
| struct device *dev; |
| struct pinctrl_dev *pctl_dev; |
| const struct cv1800_pinctrl_data *data; |
| struct pinctrl_desc pdesc; |
| u32 *power_cfg; |
| |
| struct mutex mutex; |
| raw_spinlock_t lock; |
| |
| void __iomem *regs[2]; |
| }; |
| |
| struct cv1800_pin_mux_config { |
| struct cv1800_pin *pin; |
| u32 config; |
| }; |
| |
| static unsigned int cv1800_dt_get_pin(u32 value) |
| { |
| return value & GENMASK(15, 0); |
| } |
| |
| static unsigned int cv1800_dt_get_pin_mux(u32 value) |
| { |
| return (value >> 16) & GENMASK(7, 0); |
| } |
| |
| static unsigned int cv1800_dt_get_pin_mux2(u32 value) |
| { |
| return (value >> 24) & GENMASK(7, 0); |
| } |
| |
| #define cv1800_pinctrl_get_component_addr(pctrl, _comp) \ |
| ((pctrl)->regs[(_comp)->area] + (_comp)->offset) |
| |
| static int cv1800_cmp_pin(const void *key, const void *pivot) |
| { |
| const struct cv1800_pin *pin = pivot; |
| int pin_id = (long)key; |
| int pivid = pin->pin; |
| |
| return pin_id - pivid; |
| } |
| |
| static int cv1800_set_power_cfg(struct cv1800_pinctrl *pctrl, |
| u8 domain, u32 cfg) |
| { |
| if (domain >= pctrl->data->npd) |
| return -ENOTSUPP; |
| |
| if (pctrl->power_cfg[domain] && pctrl->power_cfg[domain] != cfg) |
| return -EINVAL; |
| |
| pctrl->power_cfg[domain] = cfg; |
| |
| return 0; |
| } |
| |
| static int cv1800_get_power_cfg(struct cv1800_pinctrl *pctrl, |
| u8 domain) |
| { |
| return pctrl->power_cfg[domain]; |
| } |
| |
| static struct cv1800_pin *cv1800_get_pin(struct cv1800_pinctrl *pctrl, |
| unsigned long pin) |
| { |
| return bsearch((void *)pin, pctrl->data->pindata, pctrl->data->npins, |
| sizeof(struct cv1800_pin), cv1800_cmp_pin); |
| } |
| |
| #define PIN_BGA_ID_OFFSET 8 |
| #define PIN_BGA_ID_MASK 0xff |
| |
| static const char *const io_type_desc[] = { |
| "1V8", |
| "18OD33", |
| "AUDIO", |
| "ETH" |
| }; |
| |
| static const char *cv1800_get_power_cfg_desc(struct cv1800_pinctrl *pctrl, |
| u8 domain) |
| { |
| return pctrl->data->pdnames[domain]; |
| } |
| |
| static void cv1800_pctrl_dbg_show(struct pinctrl_dev *pctldev, |
| struct seq_file *seq, unsigned int pin_id) |
| { |
| struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); |
| enum cv1800_pin_io_type type = cv1800_pin_io_type(pin); |
| u32 value; |
| void __iomem *reg; |
| |
| if (pin->pin >> PIN_BGA_ID_OFFSET) |
| seq_printf(seq, "pos: %c%u ", |
| 'A' + (pin->pin >> PIN_BGA_ID_OFFSET) - 1, |
| pin->pin & PIN_BGA_ID_MASK); |
| else |
| seq_printf(seq, "pos: %u ", pin->pin); |
| |
| seq_printf(seq, "power-domain: %s ", |
| cv1800_get_power_cfg_desc(pctrl, pin->power_domain)); |
| seq_printf(seq, "type: %s ", io_type_desc[type]); |
| |
| reg = cv1800_pinctrl_get_component_addr(pctrl, &pin->mux); |
| value = readl(reg); |
| seq_printf(seq, "mux: 0x%08x ", value); |
| |
| if (pin->flags & CV1800_PIN_HAVE_MUX2) { |
| reg = cv1800_pinctrl_get_component_addr(pctrl, &pin->mux2); |
| value = readl(reg); |
| seq_printf(seq, "mux2: 0x%08x ", value); |
| } |
| |
| if (type == IO_TYPE_1V8_ONLY || type == IO_TYPE_1V8_OR_3V3) { |
| reg = cv1800_pinctrl_get_component_addr(pctrl, &pin->conf); |
| value = readl(reg); |
| seq_printf(seq, "conf: 0x%08x ", value); |
| } |
| } |
| |
| static int cv1800_verify_pinmux_config(const struct cv1800_pin_mux_config *config) |
| { |
| unsigned int mux = cv1800_dt_get_pin_mux(config->config); |
| unsigned int mux2 = cv1800_dt_get_pin_mux2(config->config); |
| |
| if (mux > config->pin->mux.max) |
| return -EINVAL; |
| |
| if (config->pin->flags & CV1800_PIN_HAVE_MUX2) { |
| if (mux != config->pin->mux2.pfunc) |
| return -EINVAL; |
| |
| if (mux2 > config->pin->mux2.max) |
| return -EINVAL; |
| } else { |
| if (mux2 != PIN_MUX_INVALD) |
| return -ENOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| static int cv1800_verify_pin_group(const struct cv1800_pin_mux_config *mux, |
| unsigned long npins) |
| { |
| enum cv1800_pin_io_type type; |
| u8 power_domain; |
| int i; |
| |
| if (npins == 1) |
| return 0; |
| |
| type = cv1800_pin_io_type(mux[0].pin); |
| power_domain = mux[0].pin->power_domain; |
| |
| for (i = 0; i < npins; i++) { |
| if (type != cv1800_pin_io_type(mux[i].pin) || |
| power_domain != mux[i].pin->power_domain) |
| return -ENOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| static int cv1800_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev, |
| struct device_node *np, |
| struct pinctrl_map **maps, |
| unsigned int *num_maps) |
| { |
| struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| struct device *dev = pctrl->dev; |
| struct device_node *child; |
| struct pinctrl_map *map; |
| const char **grpnames; |
| const char *grpname; |
| int ngroups = 0; |
| int nmaps = 0; |
| int ret; |
| |
| for_each_available_child_of_node(np, child) |
| ngroups += 1; |
| |
| grpnames = devm_kcalloc(dev, ngroups, sizeof(*grpnames), GFP_KERNEL); |
| if (!grpnames) |
| return -ENOMEM; |
| |
| map = kcalloc(ngroups * 2, sizeof(*map), GFP_KERNEL); |
| if (!map) |
| return -ENOMEM; |
| |
| ngroups = 0; |
| mutex_lock(&pctrl->mutex); |
| for_each_available_child_of_node(np, child) { |
| int npins = of_property_count_u32_elems(child, "pinmux"); |
| unsigned int *pins; |
| struct cv1800_pin_mux_config *pinmuxs; |
| u32 config, power; |
| int i; |
| |
| if (npins < 1) { |
| dev_err(dev, "invalid pinctrl group %pOFn.%pOFn\n", |
| np, child); |
| ret = -EINVAL; |
| goto dt_failed; |
| } |
| |
| grpname = devm_kasprintf(dev, GFP_KERNEL, "%pOFn.%pOFn", |
| np, child); |
| if (!grpname) { |
| ret = -ENOMEM; |
| goto dt_failed; |
| } |
| |
| grpnames[ngroups++] = grpname; |
| |
| pins = devm_kcalloc(dev, npins, sizeof(*pins), GFP_KERNEL); |
| if (!pins) { |
| ret = -ENOMEM; |
| goto dt_failed; |
| } |
| |
| pinmuxs = devm_kcalloc(dev, npins, sizeof(*pinmuxs), GFP_KERNEL); |
| if (!pinmuxs) { |
| ret = -ENOMEM; |
| goto dt_failed; |
| } |
| |
| for (i = 0; i < npins; i++) { |
| ret = of_property_read_u32_index(child, "pinmux", |
| i, &config); |
| if (ret) |
| goto dt_failed; |
| |
| pins[i] = cv1800_dt_get_pin(config); |
| pinmuxs[i].config = config; |
| pinmuxs[i].pin = cv1800_get_pin(pctrl, pins[i]); |
| |
| if (!pinmuxs[i].pin) { |
| dev_err(dev, "failed to get pin %d\n", pins[i]); |
| ret = -ENODEV; |
| goto dt_failed; |
| } |
| |
| ret = cv1800_verify_pinmux_config(&pinmuxs[i]); |
| if (ret) { |
| dev_err(dev, "group %s pin %d is invalid\n", |
| grpname, i); |
| goto dt_failed; |
| } |
| } |
| |
| ret = cv1800_verify_pin_group(pinmuxs, npins); |
| if (ret) { |
| dev_err(dev, "group %s is invalid\n", grpname); |
| goto dt_failed; |
| } |
| |
| ret = of_property_read_u32(child, "power-source", &power); |
| if (ret) |
| goto dt_failed; |
| |
| if (!(power == PIN_POWER_STATE_3V3 || power == PIN_POWER_STATE_1V8)) { |
| dev_err(dev, "group %s have unsupported power: %u\n", |
| grpname, power); |
| ret = -ENOTSUPP; |
| goto dt_failed; |
| } |
| |
| ret = cv1800_set_power_cfg(pctrl, pinmuxs[0].pin->power_domain, |
| power); |
| if (ret) |
| goto dt_failed; |
| |
| map[nmaps].type = PIN_MAP_TYPE_MUX_GROUP; |
| map[nmaps].data.mux.function = np->name; |
| map[nmaps].data.mux.group = grpname; |
| nmaps += 1; |
| |
| ret = pinconf_generic_parse_dt_config(child, pctldev, |
| &map[nmaps].data.configs.configs, |
| &map[nmaps].data.configs.num_configs); |
| if (ret) { |
| dev_err(dev, "failed to parse pin config of group %s: %d\n", |
| grpname, ret); |
| goto dt_failed; |
| } |
| |
| ret = pinctrl_generic_add_group(pctldev, grpname, |
| pins, npins, pinmuxs); |
| if (ret < 0) { |
| dev_err(dev, "failed to add group %s: %d\n", grpname, ret); |
| goto dt_failed; |
| } |
| |
| /* don't create a map if there are no pinconf settings */ |
| if (map[nmaps].data.configs.num_configs == 0) |
| continue; |
| |
| map[nmaps].type = PIN_MAP_TYPE_CONFIGS_GROUP; |
| map[nmaps].data.configs.group_or_pin = grpname; |
| nmaps += 1; |
| } |
| |
| ret = pinmux_generic_add_function(pctldev, np->name, |
| grpnames, ngroups, NULL); |
| if (ret < 0) { |
| dev_err(dev, "error adding function %s: %d\n", np->name, ret); |
| goto function_failed; |
| } |
| |
| *maps = map; |
| *num_maps = nmaps; |
| mutex_unlock(&pctrl->mutex); |
| |
| return 0; |
| |
| dt_failed: |
| of_node_put(child); |
| function_failed: |
| pinctrl_utils_free_map(pctldev, map, nmaps); |
| mutex_unlock(&pctrl->mutex); |
| return ret; |
| } |
| |
| static const struct pinctrl_ops cv1800_pctrl_ops = { |
| .get_groups_count = pinctrl_generic_get_group_count, |
| .get_group_name = pinctrl_generic_get_group_name, |
| .get_group_pins = pinctrl_generic_get_group_pins, |
| .pin_dbg_show = cv1800_pctrl_dbg_show, |
| .dt_node_to_map = cv1800_pctrl_dt_node_to_map, |
| .dt_free_map = pinctrl_utils_free_map, |
| }; |
| |
| static int cv1800_pmx_set_mux(struct pinctrl_dev *pctldev, |
| unsigned int fsel, unsigned int gsel) |
| { |
| struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| const struct group_desc *group; |
| const struct cv1800_pin_mux_config *configs; |
| unsigned int i; |
| |
| group = pinctrl_generic_get_group(pctldev, gsel); |
| if (!group) |
| return -EINVAL; |
| |
| configs = group->data; |
| |
| for (i = 0; i < group->grp.npins; i++) { |
| const struct cv1800_pin *pin = configs[i].pin; |
| u32 value = configs[i].config; |
| void __iomem *reg_mux; |
| void __iomem *reg_mux2; |
| unsigned long flags; |
| u32 mux; |
| u32 mux2; |
| |
| reg_mux = cv1800_pinctrl_get_component_addr(pctrl, &pin->mux); |
| reg_mux2 = cv1800_pinctrl_get_component_addr(pctrl, &pin->mux2); |
| mux = cv1800_dt_get_pin_mux(value); |
| mux2 = cv1800_dt_get_pin_mux2(value); |
| |
| raw_spin_lock_irqsave(&pctrl->lock, flags); |
| writel_relaxed(mux, reg_mux); |
| if (mux2 != PIN_MUX_INVALD) |
| writel_relaxed(mux2, reg_mux2); |
| raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
| } |
| |
| return 0; |
| } |
| |
| static const struct pinmux_ops cv1800_pmx_ops = { |
| .get_functions_count = pinmux_generic_get_function_count, |
| .get_function_name = pinmux_generic_get_function_name, |
| .get_function_groups = pinmux_generic_get_function_groups, |
| .set_mux = cv1800_pmx_set_mux, |
| .strict = true, |
| }; |
| |
| #define PIN_IO_PULLUP BIT(2) |
| #define PIN_IO_PULLDOWN BIT(3) |
| #define PIN_IO_DRIVE GENMASK(7, 5) |
| #define PIN_IO_SCHMITT GENMASK(9, 8) |
| #define PIN_IO_BUS_HOLD BIT(10) |
| #define PIN_IO_OUT_FAST_SLEW BIT(11) |
| |
| static u32 cv1800_pull_down_typical_resistor(struct cv1800_pinctrl *pctrl, |
| struct cv1800_pin *pin) |
| { |
| return pctrl->data->vddio_ops->get_pull_down(pin, pctrl->power_cfg); |
| } |
| |
| static u32 cv1800_pull_up_typical_resistor(struct cv1800_pinctrl *pctrl, |
| struct cv1800_pin *pin) |
| { |
| return pctrl->data->vddio_ops->get_pull_up(pin, pctrl->power_cfg); |
| } |
| |
| static int cv1800_pinctrl_oc2reg(struct cv1800_pinctrl *pctrl, |
| struct cv1800_pin *pin, u32 target) |
| { |
| const u32 *map; |
| int i, len; |
| |
| len = pctrl->data->vddio_ops->get_oc_map(pin, pctrl->power_cfg, &map); |
| if (len < 0) |
| return len; |
| |
| for (i = 0; i < len; i++) { |
| if (map[i] >= target) |
| return i; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int cv1800_pinctrl_reg2oc(struct cv1800_pinctrl *pctrl, |
| struct cv1800_pin *pin, u32 reg) |
| { |
| const u32 *map; |
| int len; |
| |
| len = pctrl->data->vddio_ops->get_oc_map(pin, pctrl->power_cfg, &map); |
| if (len < 0) |
| return len; |
| |
| if (reg >= len) |
| return -EINVAL; |
| |
| return map[reg]; |
| } |
| |
| static int cv1800_pinctrl_schmitt2reg(struct cv1800_pinctrl *pctrl, |
| struct cv1800_pin *pin, u32 target) |
| { |
| const u32 *map; |
| int i, len; |
| |
| len = pctrl->data->vddio_ops->get_schmitt_map(pin, pctrl->power_cfg, |
| &map); |
| if (len < 0) |
| return len; |
| |
| for (i = 0; i < len; i++) { |
| if (map[i] == target) |
| return i; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int cv1800_pinctrl_reg2schmitt(struct cv1800_pinctrl *pctrl, |
| struct cv1800_pin *pin, u32 reg) |
| { |
| const u32 *map; |
| int len; |
| |
| len = pctrl->data->vddio_ops->get_schmitt_map(pin, pctrl->power_cfg, |
| &map); |
| if (len < 0) |
| return len; |
| |
| if (reg >= len) |
| return -EINVAL; |
| |
| return map[reg]; |
| } |
| |
| static int cv1800_pconf_get(struct pinctrl_dev *pctldev, |
| unsigned int pin_id, unsigned long *config) |
| { |
| struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| int param = pinconf_to_config_param(*config); |
| struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); |
| enum cv1800_pin_io_type type; |
| u32 value; |
| u32 arg; |
| bool enabled; |
| int ret; |
| |
| if (!pin) |
| return -EINVAL; |
| |
| type = cv1800_pin_io_type(pin); |
| if (type == IO_TYPE_ETH || type == IO_TYPE_AUDIO) |
| return -ENOTSUPP; |
| |
| value = readl(cv1800_pinctrl_get_component_addr(pctrl, &pin->conf)); |
| |
| switch (param) { |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| enabled = FIELD_GET(PIN_IO_PULLDOWN, value); |
| arg = cv1800_pull_down_typical_resistor(pctrl, pin); |
| break; |
| case PIN_CONFIG_BIAS_PULL_UP: |
| enabled = FIELD_GET(PIN_IO_PULLUP, value); |
| arg = cv1800_pull_up_typical_resistor(pctrl, pin); |
| break; |
| case PIN_CONFIG_DRIVE_STRENGTH_UA: |
| enabled = true; |
| arg = FIELD_GET(PIN_IO_DRIVE, value); |
| ret = cv1800_pinctrl_reg2oc(pctrl, pin, arg); |
| if (ret < 0) |
| return ret; |
| arg = ret; |
| break; |
| case PIN_CONFIG_INPUT_SCHMITT_UV: |
| arg = FIELD_GET(PIN_IO_SCHMITT, value); |
| ret = cv1800_pinctrl_reg2schmitt(pctrl, pin, arg); |
| if (ret < 0) |
| return ret; |
| arg = ret; |
| enabled = arg != 0; |
| break; |
| case PIN_CONFIG_POWER_SOURCE: |
| enabled = true; |
| arg = cv1800_get_power_cfg(pctrl, pin->power_domain); |
| break; |
| case PIN_CONFIG_SLEW_RATE: |
| enabled = true; |
| arg = FIELD_GET(PIN_IO_OUT_FAST_SLEW, value); |
| break; |
| case PIN_CONFIG_BIAS_BUS_HOLD: |
| arg = FIELD_GET(PIN_IO_BUS_HOLD, value); |
| enabled = arg != 0; |
| break; |
| default: |
| return -ENOTSUPP; |
| } |
| |
| *config = pinconf_to_config_packed(param, arg); |
| |
| return enabled ? 0 : -EINVAL; |
| } |
| |
| static int cv1800_pinconf_compute_config(struct cv1800_pinctrl *pctrl, |
| struct cv1800_pin *pin, |
| unsigned long *configs, |
| unsigned int num_configs, |
| u32 *value) |
| { |
| int i; |
| u32 v = 0; |
| enum cv1800_pin_io_type type; |
| int ret; |
| |
| if (!pin) |
| return -EINVAL; |
| |
| type = cv1800_pin_io_type(pin); |
| if (type == IO_TYPE_ETH || type == IO_TYPE_AUDIO) |
| return -ENOTSUPP; |
| |
| for (i = 0; i < num_configs; i++) { |
| int param = pinconf_to_config_param(configs[i]); |
| u32 arg = pinconf_to_config_argument(configs[i]); |
| |
| switch (param) { |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| v &= ~PIN_IO_PULLDOWN; |
| v |= FIELD_PREP(PIN_IO_PULLDOWN, arg); |
| break; |
| case PIN_CONFIG_BIAS_PULL_UP: |
| v &= ~PIN_IO_PULLUP; |
| v |= FIELD_PREP(PIN_IO_PULLUP, arg); |
| break; |
| case PIN_CONFIG_DRIVE_STRENGTH_UA: |
| ret = cv1800_pinctrl_oc2reg(pctrl, pin, arg); |
| if (ret < 0) |
| return ret; |
| v &= ~PIN_IO_DRIVE; |
| v |= FIELD_PREP(PIN_IO_DRIVE, ret); |
| break; |
| case PIN_CONFIG_INPUT_SCHMITT_UV: |
| ret = cv1800_pinctrl_schmitt2reg(pctrl, pin, arg); |
| if (ret < 0) |
| return ret; |
| v &= ~PIN_IO_SCHMITT; |
| v |= FIELD_PREP(PIN_IO_SCHMITT, ret); |
| break; |
| case PIN_CONFIG_POWER_SOURCE: |
| /* Ignore power source as it is always fixed */ |
| break; |
| case PIN_CONFIG_SLEW_RATE: |
| v &= ~PIN_IO_OUT_FAST_SLEW; |
| v |= FIELD_PREP(PIN_IO_OUT_FAST_SLEW, arg); |
| break; |
| case PIN_CONFIG_BIAS_BUS_HOLD: |
| v &= ~PIN_IO_BUS_HOLD; |
| v |= FIELD_PREP(PIN_IO_BUS_HOLD, arg); |
| break; |
| default: |
| return -ENOTSUPP; |
| } |
| } |
| |
| *value = v; |
| |
| return 0; |
| } |
| |
| static int cv1800_pin_set_config(struct cv1800_pinctrl *pctrl, |
| unsigned int pin_id, |
| u32 value) |
| { |
| struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); |
| unsigned long flags; |
| void __iomem *addr; |
| |
| if (!pin) |
| return -EINVAL; |
| |
| addr = cv1800_pinctrl_get_component_addr(pctrl, &pin->conf); |
| |
| raw_spin_lock_irqsave(&pctrl->lock, flags); |
| writel(value, addr); |
| raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
| |
| return 0; |
| } |
| |
| static int cv1800_pconf_set(struct pinctrl_dev *pctldev, |
| unsigned int pin_id, unsigned long *configs, |
| unsigned int num_configs) |
| { |
| struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); |
| u32 value; |
| |
| if (!pin) |
| return -ENODEV; |
| |
| if (cv1800_pinconf_compute_config(pctrl, pin, |
| configs, num_configs, &value)) |
| return -ENOTSUPP; |
| |
| return cv1800_pin_set_config(pctrl, pin_id, value); |
| } |
| |
| static int cv1800_pconf_group_set(struct pinctrl_dev *pctldev, |
| unsigned int gsel, |
| unsigned long *configs, |
| unsigned int num_configs) |
| { |
| struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| const struct group_desc *group; |
| const struct cv1800_pin_mux_config *pinmuxs; |
| u32 value; |
| int i; |
| |
| group = pinctrl_generic_get_group(pctldev, gsel); |
| if (!group) |
| return -EINVAL; |
| |
| pinmuxs = group->data; |
| |
| if (cv1800_pinconf_compute_config(pctrl, pinmuxs[0].pin, |
| configs, num_configs, &value)) |
| return -ENOTSUPP; |
| |
| for (i = 0; i < group->grp.npins; i++) |
| cv1800_pin_set_config(pctrl, group->grp.pins[i], value); |
| |
| return 0; |
| } |
| |
| static const struct pinconf_ops cv1800_pconf_ops = { |
| .pin_config_get = cv1800_pconf_get, |
| .pin_config_set = cv1800_pconf_set, |
| .pin_config_group_set = cv1800_pconf_group_set, |
| .is_generic = true, |
| }; |
| |
| int cv1800_pinctrl_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct cv1800_pinctrl *pctrl; |
| const struct cv1800_pinctrl_data *pctrl_data; |
| int ret; |
| |
| pctrl_data = device_get_match_data(dev); |
| if (!pctrl_data) |
| return -ENODEV; |
| |
| if (pctrl_data->npins == 0 || pctrl_data->npd == 0) |
| return dev_err_probe(dev, -EINVAL, "invalid pin data\n"); |
| |
| pctrl = devm_kzalloc(dev, sizeof(*pctrl), GFP_KERNEL); |
| if (!pctrl) |
| return -ENOMEM; |
| |
| pctrl->power_cfg = devm_kcalloc(dev, pctrl_data->npd, |
| sizeof(u32), GFP_KERNEL); |
| if (!pctrl->power_cfg) |
| return -ENOMEM; |
| |
| pctrl->regs[0] = devm_platform_ioremap_resource_byname(pdev, "sys"); |
| if (IS_ERR(pctrl->regs[0])) |
| return PTR_ERR(pctrl->regs[0]); |
| |
| pctrl->regs[1] = devm_platform_ioremap_resource_byname(pdev, "rtc"); |
| if (IS_ERR(pctrl->regs[1])) |
| return PTR_ERR(pctrl->regs[1]); |
| |
| pctrl->pdesc.name = dev_name(dev); |
| pctrl->pdesc.pins = pctrl_data->pins; |
| pctrl->pdesc.npins = pctrl_data->npins; |
| pctrl->pdesc.pctlops = &cv1800_pctrl_ops; |
| pctrl->pdesc.pmxops = &cv1800_pmx_ops; |
| pctrl->pdesc.confops = &cv1800_pconf_ops; |
| pctrl->pdesc.owner = THIS_MODULE; |
| |
| pctrl->data = pctrl_data; |
| pctrl->dev = dev; |
| raw_spin_lock_init(&pctrl->lock); |
| mutex_init(&pctrl->mutex); |
| |
| platform_set_drvdata(pdev, pctrl); |
| |
| ret = devm_pinctrl_register_and_init(dev, &pctrl->pdesc, |
| pctrl, &pctrl->pctl_dev); |
| if (ret) |
| return dev_err_probe(dev, ret, |
| "fail to register pinctrl driver\n"); |
| |
| return pinctrl_enable(pctrl->pctl_dev); |
| } |
| EXPORT_SYMBOL_GPL(cv1800_pinctrl_probe); |