aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/pinctrl/sophgo/pinctrl-sophgo-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pinctrl/sophgo/pinctrl-sophgo-common.c')
-rw-r--r--drivers/pinctrl/sophgo/pinctrl-sophgo-common.c451
1 files changed, 451 insertions, 0 deletions
diff --git a/drivers/pinctrl/sophgo/pinctrl-sophgo-common.c b/drivers/pinctrl/sophgo/pinctrl-sophgo-common.c
new file mode 100644
index 000000000000..7f1fd68db19e
--- /dev/null
+++ b/drivers/pinctrl/sophgo/pinctrl-sophgo-common.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sophgo SoCs pinctrl common ops.
+ *
+ * Copyright (C) 2024 Inochi Amaoto <inochiama@outlook.com>
+ *
+ */
+
+#include <linux/bsearch.h>
+#include <linux/cleanup.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinctrl.h>
+
+#include "../pinctrl-utils.h"
+#include "../pinconf.h"
+#include "../pinmux.h"
+
+#include "pinctrl-sophgo.h"
+
+static u16 sophgo_dt_get_pin(u32 value)
+{
+ return value;
+}
+
+static int sophgo_cmp_pin(const void *key, const void *pivot)
+{
+ const struct sophgo_pin *pin = pivot;
+ int pin_id = (long)key;
+ int pivid = pin->id;
+
+ return pin_id - pivid;
+}
+
+const struct sophgo_pin *sophgo_get_pin(struct sophgo_pinctrl *pctrl,
+ unsigned long pin_id)
+{
+ return bsearch((void *)pin_id, pctrl->data->pindata, pctrl->data->npins,
+ pctrl->data->pinsize, sophgo_cmp_pin);
+}
+
+static int sophgo_verify_pinmux_config(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin_mux_config *config)
+{
+ if (pctrl->data->cfg_ops->verify_pinmux_config)
+ return pctrl->data->cfg_ops->verify_pinmux_config(config);
+ return 0;
+}
+
+static int sophgo_verify_pin_group(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin_mux_config *config,
+ unsigned int npins)
+{
+ if (pctrl->data->cfg_ops->verify_pin_group)
+ return pctrl->data->cfg_ops->verify_pin_group(config, npins);
+ return 0;
+}
+
+static int sophgo_dt_node_to_map_post(struct device_node *cur,
+ struct sophgo_pinctrl *pctrl,
+ struct sophgo_pin_mux_config *config,
+ unsigned int npins)
+{
+ if (pctrl->data->cfg_ops->dt_node_to_map_post)
+ return pctrl->data->cfg_ops->dt_node_to_map_post(cur, pctrl,
+ config, npins);
+ return 0;
+}
+
+int sophgo_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev, struct device_node *np,
+ struct pinctrl_map **maps, unsigned int *num_maps)
+{
+ struct sophgo_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;
+ guard(mutex)(&pctrl->mutex);
+ for_each_available_child_of_node(np, child) {
+ int npins = of_property_count_u32_elems(child, "pinmux");
+ unsigned int *pins;
+ struct sophgo_pin_mux_config *pinmuxs;
+ u32 config;
+ 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] = sophgo_dt_get_pin(config);
+ pinmuxs[i].config = config;
+ pinmuxs[i].pin = sophgo_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 = sophgo_verify_pinmux_config(pctrl, &pinmuxs[i]);
+ if (ret) {
+ dev_err(dev, "group %s pin %d is invalid\n",
+ grpname, i);
+ goto dt_failed;
+ }
+ }
+
+ ret = sophgo_verify_pin_group(pctrl, pinmuxs, npins);
+ if (ret) {
+ dev_err(dev, "group %s is invalid\n", grpname);
+ goto dt_failed;
+ }
+
+ ret = sophgo_dt_node_to_map_post(child, pctrl, pinmuxs, npins);
+ 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;
+
+ return 0;
+
+dt_failed:
+ of_node_put(child);
+function_failed:
+ pinctrl_utils_free_map(pctldev, map, nmaps);
+ return ret;
+}
+
+int sophgo_pmx_set_mux(struct pinctrl_dev *pctldev,
+ unsigned int fsel, unsigned int gsel)
+{
+ struct sophgo_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);
+ const struct group_desc *group;
+ const struct sophgo_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 sophgo_pin *pin = configs[i].pin;
+ u32 value = configs[i].config;
+
+ guard(raw_spinlock_irqsave)(&pctrl->lock);
+
+ pctrl->data->cfg_ops->set_pinmux_config(pctrl, pin, value);
+ }
+
+ return 0;
+}
+
+static int sophgo_pin_set_config(struct sophgo_pinctrl *pctrl,
+ unsigned int pin_id,
+ u32 value, u32 mask)
+{
+ const struct sophgo_pin *pin = sophgo_get_pin(pctrl, pin_id);
+
+ if (!pin)
+ return -EINVAL;
+
+ guard(raw_spinlock_irqsave)(&pctrl->lock);
+
+ return pctrl->data->cfg_ops->set_pinconf_config(pctrl, pin, value, mask);
+}
+
+int sophgo_pconf_set(struct pinctrl_dev *pctldev, unsigned int pin_id,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct sophgo_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);
+ const struct sophgo_pin *pin = sophgo_get_pin(pctrl, pin_id);
+ u32 value, mask;
+
+ if (!pin)
+ return -ENODEV;
+
+ if (pctrl->data->cfg_ops->compute_pinconf_config(pctrl, pin,
+ configs, num_configs,
+ &value, &mask))
+ return -ENOTSUPP;
+
+ return sophgo_pin_set_config(pctrl, pin_id, value, mask);
+}
+
+int sophgo_pconf_group_set(struct pinctrl_dev *pctldev, unsigned int gsel,
+ unsigned long *configs, unsigned int num_configs)
+{
+ struct sophgo_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);
+ const struct group_desc *group;
+ const struct sophgo_pin_mux_config *pinmuxs;
+ u32 value, mask;
+ int i;
+
+ group = pinctrl_generic_get_group(pctldev, gsel);
+ if (!group)
+ return -EINVAL;
+
+ pinmuxs = group->data;
+
+ if (pctrl->data->cfg_ops->compute_pinconf_config(pctrl, pinmuxs[0].pin,
+ configs, num_configs,
+ &value, &mask))
+ return -ENOTSUPP;
+
+ for (i = 0; i < group->grp.npins; i++)
+ sophgo_pin_set_config(pctrl, group->grp.pins[i], value, mask);
+
+ return 0;
+}
+
+u32 sophgo_pinctrl_typical_pull_down(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin *pin,
+ const u32 *power_cfg)
+{
+ return pctrl->data->vddio_ops->get_pull_down(pin, power_cfg);
+}
+
+u32 sophgo_pinctrl_typical_pull_up(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin *pin,
+ const u32 *power_cfg)
+{
+ return pctrl->data->vddio_ops->get_pull_up(pin, power_cfg);
+}
+
+int sophgo_pinctrl_oc2reg(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin *pin,
+ const u32 *power_cfg, u32 target)
+{
+ const u32 *map;
+ int i, len;
+
+ if (!pctrl->data->vddio_ops->get_oc_map)
+ return -ENOTSUPP;
+
+ len = pctrl->data->vddio_ops->get_oc_map(pin, power_cfg, &map);
+ if (len < 0)
+ return len;
+
+ for (i = 0; i < len; i++) {
+ if (map[i] >= target)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+int sophgo_pinctrl_reg2oc(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin *pin,
+ const u32 *power_cfg, u32 reg)
+{
+ const u32 *map;
+ int len;
+
+ if (!pctrl->data->vddio_ops->get_oc_map)
+ return -ENOTSUPP;
+
+ len = pctrl->data->vddio_ops->get_oc_map(pin, power_cfg, &map);
+ if (len < 0)
+ return len;
+
+ if (reg >= len)
+ return -EINVAL;
+
+ return map[reg];
+}
+
+int sophgo_pinctrl_schmitt2reg(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin *pin,
+ const u32 *power_cfg, u32 target)
+{
+ const u32 *map;
+ int i, len;
+
+ if (!pctrl->data->vddio_ops->get_schmitt_map)
+ return -ENOTSUPP;
+
+ len = pctrl->data->vddio_ops->get_schmitt_map(pin, power_cfg, &map);
+ if (len < 0)
+ return len;
+
+ for (i = 0; i < len; i++) {
+ if (map[i] == target)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+int sophgo_pinctrl_reg2schmitt(struct sophgo_pinctrl *pctrl,
+ const struct sophgo_pin *pin,
+ const u32 *power_cfg, u32 reg)
+{
+ const u32 *map;
+ int len;
+
+ if (!pctrl->data->vddio_ops->get_schmitt_map)
+ return -ENOTSUPP;
+
+ len = pctrl->data->vddio_ops->get_schmitt_map(pin, power_cfg, &map);
+ if (len < 0)
+ return len;
+
+ if (reg >= len)
+ return -EINVAL;
+
+ return map[reg];
+}
+
+int sophgo_pinctrl_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sophgo_pinctrl *pctrl;
+ const struct sophgo_pinctrl_data *pctrl_data;
+ int ret;
+
+ pctrl_data = device_get_match_data(dev);
+ if (!pctrl_data)
+ return -ENODEV;
+
+ if (pctrl_data->npins == 0)
+ return dev_err_probe(dev, -EINVAL, "invalid pin data\n");
+
+ pctrl = devm_kzalloc(dev, sizeof(*pctrl), GFP_KERNEL);
+ if (!pctrl)
+ return -ENOMEM;
+
+ pctrl->pdesc.name = dev_name(dev);
+ pctrl->pdesc.pins = pctrl_data->pins;
+ pctrl->pdesc.npins = pctrl_data->npins;
+ pctrl->pdesc.pctlops = pctrl_data->pctl_ops;
+ pctrl->pdesc.pmxops = pctrl_data->pmx_ops;
+ pctrl->pdesc.confops = pctrl_data->pconf_ops;
+ pctrl->pdesc.owner = THIS_MODULE;
+
+ pctrl->data = pctrl_data;
+ pctrl->dev = dev;
+ raw_spin_lock_init(&pctrl->lock);
+ mutex_init(&pctrl->mutex);
+
+ ret = pctrl->data->cfg_ops->pctrl_init(pdev, pctrl);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, pctrl);
+
+ ret = devm_pinctrl_register_and_init(dev, &pctrl->pdesc,
+ pctrl, &pctrl->pctrl_dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "fail to register pinctrl driver\n");
+
+ return pinctrl_enable(pctrl->pctrl_dev);
+}
+EXPORT_SYMBOL_GPL(sophgo_pinctrl_probe);
+
+MODULE_DESCRIPTION("Common pinctrl helper function for the Sophgo SoC");
+MODULE_LICENSE("GPL");