// SPDX-License-Identifier: GPL-2.0 /* * Sophgo CV18XX SoCs pinctrl driver. * * Copyright (C) 2024 Inochi Amaoto * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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);