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


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