Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Inochi Amaoto 2138 100.00% 6 100.00%
Total 2138 6


// 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);