diff options
Diffstat (limited to 'drivers/pinctrl/sophgo/pinctrl-cv18xx.c')
| -rw-r--r-- | drivers/pinctrl/sophgo/pinctrl-cv18xx.c | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/drivers/pinctrl/sophgo/pinctrl-cv18xx.c b/drivers/pinctrl/sophgo/pinctrl-cv18xx.c new file mode 100644 index 000000000000..c3a2dcf71f2a --- /dev/null +++ b/drivers/pinctrl/sophgo/pinctrl-cv18xx.c @@ -0,0 +1,451 @@ +// 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/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 "../pinctrl-utils.h" +#include "../pinmux.h" +#include "pinctrl-cv18xx.h" + +struct cv1800_priv { + u32 *power_cfg; + void __iomem *regs[2]; +}; + +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_set_power_cfg(struct sophgo_pinctrl *pctrl, + u8 domain, u32 cfg) +{ + struct cv1800_priv *priv = pctrl->priv_ctrl; + + if (domain >= pctrl->data->npds) + return -ENOTSUPP; + + if (priv->power_cfg[domain] && priv->power_cfg[domain] != cfg) + return -EINVAL; + + priv->power_cfg[domain] = cfg; + + return 0; +} + +static int cv1800_get_power_cfg(struct sophgo_pinctrl *pctrl, + u8 domain) +{ + struct cv1800_priv *priv = pctrl->priv_ctrl; + + return priv->power_cfg[domain]; +} + +#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 sophgo_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 sophgo_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct cv1800_priv *priv = pctrl->priv_ctrl; + const struct sophgo_pin *sp = sophgo_get_pin(pctrl, pin_id); + const struct cv1800_pin *pin = sophgo_to_cv1800_pin(sp); + enum cv1800_pin_io_type type = cv1800_pin_io_type(pin); + u32 pin_hwid = pin->pin.id; + u32 value; + void __iomem *reg; + + if (pin_hwid >> PIN_BGA_ID_OFFSET) + seq_printf(seq, "pos: %c%u ", + 'A' + (pin_hwid >> PIN_BGA_ID_OFFSET) - 1, + pin_hwid & PIN_BGA_ID_MASK); + else + seq_printf(seq, "pos: %u ", pin_hwid); + + 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(priv, &pin->mux); + value = readl(reg); + seq_printf(seq, "mux: 0x%08x ", value); + + if (pin->pin.flags & CV1800_PIN_HAVE_MUX2) { + reg = cv1800_pinctrl_get_component_addr(priv, &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(priv, &pin->conf); + value = readl(reg); + seq_printf(seq, "conf: 0x%08x ", value); + } +} + +static int cv1800_verify_pinmux_config(const struct sophgo_pin_mux_config *config) +{ + struct cv1800_pin *pin = sophgo_to_cv1800_pin(config->pin); + unsigned int mux = cv1800_dt_get_pin_mux(config->config); + unsigned int mux2 = cv1800_dt_get_pin_mux2(config->config); + + if (mux > pin->mux.max) + return -EINVAL; + + if (pin->pin.flags & CV1800_PIN_HAVE_MUX2) { + if (mux != pin->mux2.pfunc) + return -EINVAL; + + if (mux2 > pin->mux2.max) + return -EINVAL; + } else { + if (mux2 != PIN_MUX_INVALD) + return -ENOTSUPP; + } + + return 0; +} + +static int cv1800_verify_pin_group(const struct sophgo_pin_mux_config *mux, + unsigned int npins) +{ + struct cv1800_pin *pin; + enum cv1800_pin_io_type type; + u8 power_domain; + int i; + + if (npins == 1) + return 0; + + pin = sophgo_to_cv1800_pin(mux[0].pin); + type = cv1800_pin_io_type(pin); + power_domain = pin->power_domain; + + for (i = 0; i < npins; i++) { + pin = sophgo_to_cv1800_pin(mux[i].pin); + + if (type != cv1800_pin_io_type(pin) || + power_domain != pin->power_domain) + return -ENOTSUPP; + } + + return 0; +} + +static int cv1800_dt_node_to_map_post(struct device_node *cur, + struct sophgo_pinctrl *pctrl, + struct sophgo_pin_mux_config *pinmuxs, + unsigned int npins) +{ + const struct cv1800_pin *pin = sophgo_to_cv1800_pin(pinmuxs[0].pin); + u32 power; + int ret; + + ret = of_property_read_u32(cur, "power-source", &power); + if (ret) + return ret; + + if (!(power == PIN_POWER_STATE_3V3 || power == PIN_POWER_STATE_1V8)) + return -ENOTSUPP; + + return cv1800_set_power_cfg(pctrl, pin->power_domain, power); +} + +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 = sophgo_pctrl_dt_node_to_map, + .dt_free_map = pinctrl_utils_free_map, +}; +EXPORT_SYMBOL_GPL(cv1800_pctrl_ops); + +static void cv1800_set_pinmux_config(struct sophgo_pinctrl *pctrl, + const struct sophgo_pin *sp, u32 config) +{ + const struct cv1800_pin *pin = sophgo_to_cv1800_pin(sp); + struct cv1800_priv *priv = pctrl->priv_ctrl; + void __iomem *reg_mux; + void __iomem *reg_mux2; + u32 mux; + u32 mux2; + + reg_mux = cv1800_pinctrl_get_component_addr(priv, &pin->mux); + reg_mux2 = cv1800_pinctrl_get_component_addr(priv, &pin->mux2); + mux = cv1800_dt_get_pin_mux(config); + mux2 = cv1800_dt_get_pin_mux2(config); + + writel_relaxed(mux, reg_mux); + if (mux2 != PIN_MUX_INVALD) + writel_relaxed(mux2, reg_mux2); +} + +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 = sophgo_pmx_set_mux, + .strict = true, +}; +EXPORT_SYMBOL_GPL(cv1800_pmx_ops); + +#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 int cv1800_pconf_get(struct pinctrl_dev *pctldev, + unsigned int pin_id, unsigned long *config) +{ + struct sophgo_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct cv1800_priv *priv = pctrl->priv_ctrl; + int param = pinconf_to_config_param(*config); + const struct sophgo_pin *sp = sophgo_get_pin(pctrl, pin_id); + const struct cv1800_pin *pin = sophgo_to_cv1800_pin(sp); + 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(priv, &pin->conf)); + + switch (param) { + case PIN_CONFIG_BIAS_PULL_DOWN: + enabled = FIELD_GET(PIN_IO_PULLDOWN, value); + arg = sophgo_pinctrl_typical_pull_down(pctrl, sp, priv->power_cfg); + break; + case PIN_CONFIG_BIAS_PULL_UP: + enabled = FIELD_GET(PIN_IO_PULLUP, value); + arg = sophgo_pinctrl_typical_pull_up(pctrl, sp, priv->power_cfg); + break; + case PIN_CONFIG_DRIVE_STRENGTH_UA: + enabled = true; + arg = FIELD_GET(PIN_IO_DRIVE, value); + ret = sophgo_pinctrl_reg2oc(pctrl, sp, priv->power_cfg, arg); + if (ret < 0) + return ret; + arg = ret; + break; + case PIN_CONFIG_INPUT_SCHMITT_UV: + arg = FIELD_GET(PIN_IO_SCHMITT, value); + ret = sophgo_pinctrl_reg2schmitt(pctrl, sp, priv->power_cfg, 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 sophgo_pinctrl *pctrl, + const struct sophgo_pin *sp, + unsigned long *configs, + unsigned int num_configs, + u32 *value, u32 *mask) +{ + struct cv1800_priv *priv = pctrl->priv_ctrl; + const struct cv1800_pin *pin = sophgo_to_cv1800_pin(sp); + int i; + u32 v = 0, m = 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); + m |= PIN_IO_PULLDOWN; + break; + case PIN_CONFIG_BIAS_PULL_UP: + v &= ~PIN_IO_PULLUP; + v |= FIELD_PREP(PIN_IO_PULLUP, arg); + m |= PIN_IO_PULLUP; + break; + case PIN_CONFIG_DRIVE_STRENGTH_UA: + ret = sophgo_pinctrl_oc2reg(pctrl, sp, + priv->power_cfg, arg); + if (ret < 0) + return ret; + v &= ~PIN_IO_DRIVE; + v |= FIELD_PREP(PIN_IO_DRIVE, ret); + m |= PIN_IO_DRIVE; + break; + case PIN_CONFIG_INPUT_SCHMITT_UV: + ret = sophgo_pinctrl_schmitt2reg(pctrl, sp, + priv->power_cfg, arg); + if (ret < 0) + return ret; + v &= ~PIN_IO_SCHMITT; + v |= FIELD_PREP(PIN_IO_SCHMITT, ret); + m |= PIN_IO_SCHMITT; + 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); + m |= PIN_IO_OUT_FAST_SLEW; + break; + case PIN_CONFIG_BIAS_BUS_HOLD: + v &= ~PIN_IO_BUS_HOLD; + v |= FIELD_PREP(PIN_IO_BUS_HOLD, arg); + m |= PIN_IO_BUS_HOLD; + break; + default: + return -ENOTSUPP; + } + } + + *value = v; + *mask = m; + + return 0; +} + +static int cv1800_set_pinconf_config(struct sophgo_pinctrl *pctrl, + const struct sophgo_pin *sp, + u32 value, u32 mask) +{ + struct cv1800_priv *priv = pctrl->priv_ctrl; + struct cv1800_pin *pin = sophgo_to_cv1800_pin(sp); + void __iomem *addr; + u32 reg; + + addr = cv1800_pinctrl_get_component_addr(priv, &pin->conf); + + reg = readl(addr); + reg &= ~mask; + reg |= value; + writel(reg, addr); + + return 0; +} + +const struct pinconf_ops cv1800_pconf_ops = { + .pin_config_get = cv1800_pconf_get, + .pin_config_set = sophgo_pconf_set, + .pin_config_group_set = sophgo_pconf_group_set, + .is_generic = true, +}; +EXPORT_SYMBOL_GPL(cv1800_pconf_ops); + +static int cv1800_pinctrl_init(struct platform_device *pdev, + struct sophgo_pinctrl *pctrl) +{ + const struct sophgo_pinctrl_data *pctrl_data = pctrl->data; + struct cv1800_priv *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct cv1800_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->power_cfg = devm_kcalloc(&pdev->dev, pctrl_data->npds, + sizeof(u32), GFP_KERNEL); + if (!priv->power_cfg) + return -ENOMEM; + + priv->regs[0] = devm_platform_ioremap_resource_byname(pdev, "sys"); + if (IS_ERR(priv->regs[0])) + return PTR_ERR(priv->regs[0]); + + priv->regs[1] = devm_platform_ioremap_resource_byname(pdev, "rtc"); + if (IS_ERR(priv->regs[1])) + return PTR_ERR(priv->regs[1]); + + pctrl->priv_ctrl = priv; + + return 0; +} + +const struct sophgo_cfg_ops cv1800_cfg_ops = { + .pctrl_init = cv1800_pinctrl_init, + .verify_pinmux_config = cv1800_verify_pinmux_config, + .verify_pin_group = cv1800_verify_pin_group, + .dt_node_to_map_post = cv1800_dt_node_to_map_post, + .compute_pinconf_config = cv1800_pinconf_compute_config, + .set_pinconf_config = cv1800_set_pinconf_config, + .set_pinmux_config = cv1800_set_pinmux_config, +}; +EXPORT_SYMBOL_GPL(cv1800_cfg_ops); |
