mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-18 22:14:16 +00:00

Since different series of the Sophgo chip share a common pinctrl data structure. It is necessary to add a common probe function to alloc the this data structure. Add pctrl_init callback to allow soc to perform its own initialization. Signed-off-by: Inochi Amaoto <inochiama@gmail.com> Link: https://lore.kernel.org/20250211051801.470800-5-inochiama@gmail.com Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
451 lines
12 KiB
C
451 lines
12 KiB
C
// 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);
|