| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Sophgo sg2042 SoCs pinctrl driver. |
| * |
| * Copyright (C) 2024 Inochi Amaoto <inochiama@outlook.com> |
| * |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/bits.h> |
| #include <linux/cleanup.h> |
| #include <linux/export.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.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 "../pinctrl-utils.h" |
| #include "../pinmux.h" |
| |
| #include "pinctrl-sg2042.h" |
| |
| #define PIN_IO_PULL_ONE_ENABLE BIT(0) |
| #define PIN_IO_PULL_DIR_UP (BIT(1) | PIN_IO_PULL_ONE_ENABLE) |
| #define PIN_IO_PULL_DIR_DOWN (0 | PIN_IO_PULL_ONE_ENABLE) |
| #define PIN_IO_PULL_ONE_MASK GENMASK(1, 0) |
| |
| #define PIN_IO_PULL_UP BIT(2) |
| #define PIN_IO_PULL_UP_DONW BIT(3) |
| #define PIN_IO_PULL_UP_MASK GENMASK(3, 2) |
| |
| #define PIN_IO_MUX GENMASK(5, 4) |
| #define PIN_IO_DRIVE GENMASK(9, 6) |
| #define PIN_IO_SCHMITT_ENABLE BIT(10) |
| #define PIN_IO_OUTPUT_ENABLE BIT(11) |
| |
| struct sg2042_priv { |
| void __iomem *regs; |
| }; |
| |
| static u8 sg2042_dt_get_pin_mux(u32 value) |
| { |
| return value >> 16; |
| } |
| |
| static inline u32 sg2042_get_pin_reg(struct sophgo_pinctrl *pctrl, |
| const struct sophgo_pin *sp) |
| { |
| struct sg2042_priv *priv = pctrl->priv_ctrl; |
| const struct sg2042_pin *pin = sophgo_to_sg2042_pin(sp); |
| void __iomem *reg = priv->regs + pin->offset; |
| |
| if (sp->flags & PIN_FLAG_WRITE_HIGH) |
| return readl(reg) >> 16; |
| else |
| return readl(reg) & 0xffff; |
| } |
| |
| static int sg2042_set_pin_reg(struct sophgo_pinctrl *pctrl, |
| const struct sophgo_pin *sp, |
| u32 value, u32 mask) |
| { |
| struct sg2042_priv *priv = pctrl->priv_ctrl; |
| const struct sg2042_pin *pin = sophgo_to_sg2042_pin(sp); |
| void __iomem *reg = priv->regs + pin->offset; |
| u32 v = readl(reg); |
| |
| if (sp->flags & PIN_FLAG_WRITE_HIGH) { |
| v &= ~(mask << 16); |
| v |= value << 16; |
| } else { |
| v &= ~mask; |
| v |= value; |
| } |
| |
| writel(v, reg); |
| |
| return 0; |
| } |
| |
| static void sg2042_pctrl_dbg_show(struct pinctrl_dev *pctldev, |
| struct seq_file *seq, unsigned int pin_id) |
| { |
| struct sophgo_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| const struct sophgo_pin *sp = sophgo_get_pin(pctrl, pin_id); |
| u32 value, mux; |
| |
| value = sg2042_get_pin_reg(pctrl, sp); |
| mux = FIELD_GET(PIN_IO_MUX, value); |
| seq_printf(seq, "mux:%u reg:0x%04x ", mux, value); |
| } |
| |
| const struct pinctrl_ops sg2042_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 = sg2042_pctrl_dbg_show, |
| .dt_node_to_map = sophgo_pctrl_dt_node_to_map, |
| .dt_free_map = pinctrl_utils_free_map, |
| }; |
| EXPORT_SYMBOL_GPL(sg2042_pctrl_ops); |
| |
| static void sg2042_set_pinmux_config(struct sophgo_pinctrl *pctrl, |
| const struct sophgo_pin *sp, u32 config) |
| { |
| u32 mux = sg2042_dt_get_pin_mux(config); |
| |
| if (!(sp->flags & PIN_FLAG_NO_PINMUX)) |
| sg2042_set_pin_reg(pctrl, sp, mux, PIN_IO_MUX); |
| } |
| |
| const struct pinmux_ops sg2042_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(sg2042_pmx_ops); |
| |
| static int sg2042_pconf_get(struct pinctrl_dev *pctldev, |
| unsigned int pin_id, unsigned long *config) |
| { |
| struct sophgo_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); |
| int param = pinconf_to_config_param(*config); |
| const struct sophgo_pin *sp = sophgo_get_pin(pctrl, pin_id); |
| u32 value; |
| u32 arg; |
| bool enabled; |
| int ret; |
| |
| if (!sp) |
| return -EINVAL; |
| |
| value = sg2042_get_pin_reg(pctrl, sp); |
| |
| switch (param) { |
| case PIN_CONFIG_BIAS_DISABLE: |
| if (sp->flags & PIN_FLAG_ONLY_ONE_PULL) |
| arg = FIELD_GET(PIN_IO_PULL_ONE_ENABLE, value); |
| else |
| arg = FIELD_GET(PIN_IO_PULL_UP_MASK, value); |
| enabled = arg == 0; |
| break; |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| if (sp->flags & PIN_FLAG_ONLY_ONE_PULL) { |
| arg = FIELD_GET(PIN_IO_PULL_ONE_MASK, value); |
| enabled = arg == PIN_IO_PULL_DIR_DOWN; |
| } else { |
| enabled = FIELD_GET(PIN_IO_PULL_UP_DONW, value) != 0; |
| } |
| arg = sophgo_pinctrl_typical_pull_down(pctrl, sp, NULL); |
| break; |
| case PIN_CONFIG_BIAS_PULL_UP: |
| if (sp->flags & PIN_FLAG_ONLY_ONE_PULL) { |
| arg = FIELD_GET(PIN_IO_PULL_ONE_MASK, value); |
| enabled = arg == PIN_IO_PULL_DIR_UP; |
| } else { |
| enabled = FIELD_GET(PIN_IO_PULL_UP, value) != 0; |
| } |
| arg = sophgo_pinctrl_typical_pull_up(pctrl, sp, NULL); |
| break; |
| case PIN_CONFIG_DRIVE_STRENGTH_UA: |
| enabled = FIELD_GET(PIN_IO_OUTPUT_ENABLE, value) != 0; |
| arg = FIELD_GET(PIN_IO_DRIVE, value); |
| ret = sophgo_pinctrl_reg2oc(pctrl, sp, NULL, arg); |
| if (ret < 0) |
| return ret; |
| arg = ret; |
| break; |
| case PIN_CONFIG_INPUT_SCHMITT_ENABLE: |
| arg = FIELD_GET(PIN_IO_SCHMITT_ENABLE, value); |
| enabled = arg != 0; |
| break; |
| default: |
| return -ENOTSUPP; |
| } |
| |
| *config = pinconf_to_config_packed(param, arg); |
| |
| return enabled ? 0 : -EINVAL; |
| } |
| |
| static int sg2042_pinconf_compute_config(struct sophgo_pinctrl *pctrl, |
| const struct sophgo_pin *sp, |
| unsigned long *configs, |
| unsigned int num_configs, |
| u32 *value, u32 *mask) |
| { |
| int i; |
| u16 v = 0, m = 0; |
| int ret; |
| |
| if (!sp) |
| return -EINVAL; |
| |
| 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_DISABLE: |
| if (sp->flags & PIN_FLAG_ONLY_ONE_PULL) { |
| v &= ~PIN_IO_PULL_ONE_ENABLE; |
| m |= PIN_IO_PULL_ONE_ENABLE; |
| } else { |
| v &= ~PIN_IO_PULL_UP_MASK; |
| m |= PIN_IO_PULL_UP_MASK; |
| } |
| break; |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| if (sp->flags & PIN_FLAG_ONLY_ONE_PULL) { |
| v &= ~PIN_IO_PULL_ONE_MASK; |
| v |= PIN_IO_PULL_DIR_DOWN; |
| m |= PIN_IO_PULL_ONE_MASK; |
| } else { |
| v |= PIN_IO_PULL_UP_DONW; |
| m |= PIN_IO_PULL_UP_DONW; |
| } |
| break; |
| case PIN_CONFIG_BIAS_PULL_UP: |
| if (sp->flags & PIN_FLAG_ONLY_ONE_PULL) { |
| v &= ~PIN_IO_PULL_ONE_MASK; |
| v |= PIN_IO_PULL_DIR_UP; |
| m |= PIN_IO_PULL_ONE_MASK; |
| } else { |
| v |= PIN_IO_PULL_UP; |
| m |= PIN_IO_PULL_UP; |
| } |
| break; |
| case PIN_CONFIG_DRIVE_STRENGTH_UA: |
| v &= ~(PIN_IO_DRIVE | PIN_IO_OUTPUT_ENABLE); |
| if (arg != 0) { |
| ret = sophgo_pinctrl_oc2reg(pctrl, sp, NULL, arg); |
| if (ret < 0) |
| return ret; |
| if (!(sp->flags & PIN_FLAG_NO_OEX_EN)) |
| v |= PIN_IO_OUTPUT_ENABLE; |
| v |= FIELD_PREP(PIN_IO_DRIVE, ret); |
| } |
| m |= PIN_IO_DRIVE | PIN_IO_OUTPUT_ENABLE; |
| break; |
| case PIN_CONFIG_INPUT_SCHMITT_ENABLE: |
| v |= PIN_IO_SCHMITT_ENABLE; |
| m |= PIN_IO_SCHMITT_ENABLE; |
| break; |
| default: |
| return -ENOTSUPP; |
| } |
| } |
| |
| *value = v; |
| *mask = m; |
| |
| return 0; |
| } |
| |
| const struct pinconf_ops sg2042_pconf_ops = { |
| .pin_config_get = sg2042_pconf_get, |
| .pin_config_set = sophgo_pconf_set, |
| .pin_config_group_set = sophgo_pconf_group_set, |
| .is_generic = true, |
| }; |
| EXPORT_SYMBOL_GPL(sg2042_pconf_ops); |
| |
| static int sophgo_pinctrl_init(struct platform_device *pdev, |
| struct sophgo_pinctrl *pctrl) |
| { |
| struct sg2042_priv *priv; |
| |
| priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| priv->regs = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(priv->regs)) |
| return PTR_ERR(priv->regs); |
| |
| pctrl->priv_ctrl = priv; |
| |
| return 0; |
| } |
| |
| const struct sophgo_cfg_ops sg2042_cfg_ops = { |
| .pctrl_init = sophgo_pinctrl_init, |
| .compute_pinconf_config = sg2042_pinconf_compute_config, |
| .set_pinconf_config = sg2042_set_pin_reg, |
| .set_pinmux_config = sg2042_set_pinmux_config, |
| }; |
| EXPORT_SYMBOL_GPL(sg2042_cfg_ops); |