| // 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/consumer.h> | 
 | #include <linux/pinctrl/machine.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 "../core.h" | 
 | #include "../pinctrl-utils.h" | 
 | #include "../pinconf.h" | 
 | #include "../pinmux.h" | 
 | #include "pinctrl-cv18xx.h" | 
 |  | 
 | struct cv1800_pinctrl { | 
 | 	struct device				*dev; | 
 | 	struct pinctrl_dev			*pctl_dev; | 
 | 	const struct cv1800_pinctrl_data	*data; | 
 | 	struct pinctrl_desc			pdesc; | 
 | 	u32					*power_cfg; | 
 |  | 
 | 	struct mutex				mutex; | 
 | 	raw_spinlock_t				lock; | 
 |  | 
 | 	void __iomem				*regs[2]; | 
 | }; | 
 |  | 
 | struct cv1800_pin_mux_config { | 
 | 	struct cv1800_pin	*pin; | 
 | 	u32			config; | 
 | }; | 
 |  | 
 | static unsigned int cv1800_dt_get_pin(u32 value) | 
 | { | 
 | 	return value & GENMASK(15, 0); | 
 | } | 
 |  | 
 | 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_cmp_pin(const void *key, const void *pivot) | 
 | { | 
 | 	const struct cv1800_pin *pin = pivot; | 
 | 	int pin_id = (long)key; | 
 | 	int pivid = pin->pin; | 
 |  | 
 | 	return pin_id - pivid; | 
 | } | 
 |  | 
 | static int cv1800_set_power_cfg(struct cv1800_pinctrl *pctrl, | 
 | 				u8 domain, u32 cfg) | 
 | { | 
 | 	if (domain >= pctrl->data->npd) | 
 | 		return -ENOTSUPP; | 
 |  | 
 | 	if (pctrl->power_cfg[domain] && pctrl->power_cfg[domain] != cfg) | 
 | 		return -EINVAL; | 
 |  | 
 | 	pctrl->power_cfg[domain] = cfg; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cv1800_get_power_cfg(struct cv1800_pinctrl *pctrl, | 
 | 				u8 domain) | 
 | { | 
 | 	return pctrl->power_cfg[domain]; | 
 | } | 
 |  | 
 | static struct cv1800_pin *cv1800_get_pin(struct cv1800_pinctrl *pctrl, | 
 | 					 unsigned long pin) | 
 | { | 
 | 	return bsearch((void *)pin, pctrl->data->pindata, pctrl->data->npins, | 
 | 		       sizeof(struct cv1800_pin), cv1800_cmp_pin); | 
 | } | 
 |  | 
 | #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 cv1800_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 cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); | 
 | 	struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); | 
 | 	enum cv1800_pin_io_type type = cv1800_pin_io_type(pin); | 
 | 	u32 value; | 
 | 	void __iomem *reg; | 
 |  | 
 | 	if (pin->pin >> PIN_BGA_ID_OFFSET) | 
 | 		seq_printf(seq, "pos: %c%u ", | 
 | 			   'A' + (pin->pin >> PIN_BGA_ID_OFFSET) - 1, | 
 | 			   pin->pin & PIN_BGA_ID_MASK); | 
 | 	else | 
 | 		seq_printf(seq, "pos: %u ", pin->pin); | 
 |  | 
 | 	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(pctrl, &pin->mux); | 
 | 	value = readl(reg); | 
 | 	seq_printf(seq, "mux: 0x%08x ", value); | 
 |  | 
 | 	if (pin->flags & CV1800_PIN_HAVE_MUX2) { | 
 | 		reg = cv1800_pinctrl_get_component_addr(pctrl, &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(pctrl, &pin->conf); | 
 | 		value = readl(reg); | 
 | 		seq_printf(seq, "conf: 0x%08x ", value); | 
 | 	} | 
 | } | 
 |  | 
 | static int cv1800_verify_pinmux_config(const struct cv1800_pin_mux_config *config) | 
 | { | 
 | 	unsigned int mux = cv1800_dt_get_pin_mux(config->config); | 
 | 	unsigned int mux2 = cv1800_dt_get_pin_mux2(config->config); | 
 |  | 
 | 	if (mux > config->pin->mux.max) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (config->pin->flags & CV1800_PIN_HAVE_MUX2) { | 
 | 		if (mux != config->pin->mux2.pfunc) | 
 | 			return -EINVAL; | 
 |  | 
 | 		if (mux2 > config->pin->mux2.max) | 
 | 			return -EINVAL; | 
 | 	} else { | 
 | 		if (mux2 != PIN_MUX_INVALD) | 
 | 			return -ENOTSUPP; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cv1800_verify_pin_group(const struct cv1800_pin_mux_config *mux, | 
 | 				   unsigned long npins) | 
 | { | 
 | 	enum cv1800_pin_io_type type; | 
 | 	u8 power_domain; | 
 | 	int i; | 
 |  | 
 | 	if (npins == 1) | 
 | 		return 0; | 
 |  | 
 | 	type = cv1800_pin_io_type(mux[0].pin); | 
 | 	power_domain = mux[0].pin->power_domain; | 
 |  | 
 | 	for (i = 0; i < npins; i++) { | 
 | 		if (type != cv1800_pin_io_type(mux[i].pin) || | 
 | 		    power_domain != mux[i].pin->power_domain) | 
 | 			return -ENOTSUPP; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cv1800_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev, | 
 | 				       struct device_node *np, | 
 | 				       struct pinctrl_map **maps, | 
 | 				       unsigned int *num_maps) | 
 | { | 
 | 	struct cv1800_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; | 
 | 	mutex_lock(&pctrl->mutex); | 
 | 	for_each_available_child_of_node(np, child) { | 
 | 		int npins = of_property_count_u32_elems(child, "pinmux"); | 
 | 		unsigned int *pins; | 
 | 		struct cv1800_pin_mux_config *pinmuxs; | 
 | 		u32 config, power; | 
 | 		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] = cv1800_dt_get_pin(config); | 
 | 			pinmuxs[i].config = config; | 
 | 			pinmuxs[i].pin = cv1800_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 = cv1800_verify_pinmux_config(&pinmuxs[i]); | 
 | 			if (ret) { | 
 | 				dev_err(dev, "group %s pin %d is invalid\n", | 
 | 					grpname, i); | 
 | 				goto dt_failed; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		ret = cv1800_verify_pin_group(pinmuxs, npins); | 
 | 		if (ret) { | 
 | 			dev_err(dev, "group %s is invalid\n", grpname); | 
 | 			goto dt_failed; | 
 | 		} | 
 |  | 
 | 		ret = of_property_read_u32(child, "power-source", &power); | 
 | 		if (ret) | 
 | 			goto dt_failed; | 
 |  | 
 | 		if (!(power == PIN_POWER_STATE_3V3 || power == PIN_POWER_STATE_1V8)) { | 
 | 			dev_err(dev, "group %s have unsupported power: %u\n", | 
 | 				grpname, power); | 
 | 			ret = -ENOTSUPP; | 
 | 			goto dt_failed; | 
 | 		} | 
 |  | 
 | 		ret = cv1800_set_power_cfg(pctrl, pinmuxs[0].pin->power_domain, | 
 | 					   power); | 
 | 		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; | 
 | 	mutex_unlock(&pctrl->mutex); | 
 |  | 
 | 	return 0; | 
 |  | 
 | dt_failed: | 
 | 	of_node_put(child); | 
 | function_failed: | 
 | 	pinctrl_utils_free_map(pctldev, map, nmaps); | 
 | 	mutex_unlock(&pctrl->mutex); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static 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		= cv1800_pctrl_dt_node_to_map, | 
 | 	.dt_free_map		= pinctrl_utils_free_map, | 
 | }; | 
 |  | 
 | static int cv1800_pmx_set_mux(struct pinctrl_dev *pctldev, | 
 | 			      unsigned int fsel, unsigned int gsel) | 
 | { | 
 | 	struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); | 
 | 	const struct group_desc *group; | 
 | 	const struct cv1800_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 cv1800_pin *pin = configs[i].pin; | 
 | 		u32 value = configs[i].config; | 
 | 		void __iomem *reg_mux; | 
 | 		void __iomem *reg_mux2; | 
 | 		unsigned long flags; | 
 | 		u32 mux; | 
 | 		u32 mux2; | 
 |  | 
 | 		reg_mux = cv1800_pinctrl_get_component_addr(pctrl, &pin->mux); | 
 | 		reg_mux2 = cv1800_pinctrl_get_component_addr(pctrl, &pin->mux2); | 
 | 		mux = cv1800_dt_get_pin_mux(value); | 
 | 		mux2 = cv1800_dt_get_pin_mux2(value); | 
 |  | 
 | 		raw_spin_lock_irqsave(&pctrl->lock, flags); | 
 | 		writel_relaxed(mux, reg_mux); | 
 | 		if (mux2 != PIN_MUX_INVALD) | 
 | 			writel_relaxed(mux2, reg_mux2); | 
 | 		raw_spin_unlock_irqrestore(&pctrl->lock, flags); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static 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		= cv1800_pmx_set_mux, | 
 | 	.strict			= true, | 
 | }; | 
 |  | 
 | #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 u32 cv1800_pull_down_typical_resistor(struct cv1800_pinctrl *pctrl, | 
 | 					     struct cv1800_pin *pin) | 
 | { | 
 | 	return pctrl->data->vddio_ops->get_pull_down(pin, pctrl->power_cfg); | 
 | } | 
 |  | 
 | static u32 cv1800_pull_up_typical_resistor(struct cv1800_pinctrl *pctrl, | 
 | 					   struct cv1800_pin *pin) | 
 | { | 
 | 	return pctrl->data->vddio_ops->get_pull_up(pin, pctrl->power_cfg); | 
 | } | 
 |  | 
 | static int cv1800_pinctrl_oc2reg(struct cv1800_pinctrl *pctrl, | 
 | 				 struct cv1800_pin *pin, u32 target) | 
 | { | 
 | 	const u32 *map; | 
 | 	int i, len; | 
 |  | 
 | 	len = pctrl->data->vddio_ops->get_oc_map(pin, pctrl->power_cfg, &map); | 
 | 	if (len < 0) | 
 | 		return len; | 
 |  | 
 | 	for (i = 0; i < len; i++) { | 
 | 		if (map[i] >= target) | 
 | 			return i; | 
 | 	} | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static int cv1800_pinctrl_reg2oc(struct cv1800_pinctrl *pctrl, | 
 | 				 struct cv1800_pin *pin, u32 reg) | 
 | { | 
 | 	const u32 *map; | 
 | 	int len; | 
 |  | 
 | 	len = pctrl->data->vddio_ops->get_oc_map(pin, pctrl->power_cfg, &map); | 
 | 	if (len < 0) | 
 | 		return len; | 
 |  | 
 | 	if (reg >= len) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return map[reg]; | 
 | } | 
 |  | 
 | static int cv1800_pinctrl_schmitt2reg(struct cv1800_pinctrl *pctrl, | 
 | 				      struct cv1800_pin *pin, u32 target) | 
 | { | 
 | 	const u32 *map; | 
 | 	int i, len; | 
 |  | 
 | 	len = pctrl->data->vddio_ops->get_schmitt_map(pin, pctrl->power_cfg, | 
 | 						      &map); | 
 | 	if (len < 0) | 
 | 		return len; | 
 |  | 
 | 	for (i = 0; i < len; i++) { | 
 | 		if (map[i] == target) | 
 | 			return i; | 
 | 	} | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static int cv1800_pinctrl_reg2schmitt(struct cv1800_pinctrl *pctrl, | 
 | 				      struct cv1800_pin *pin, u32 reg) | 
 | { | 
 | 	const u32 *map; | 
 | 	int len; | 
 |  | 
 | 	len = pctrl->data->vddio_ops->get_schmitt_map(pin, pctrl->power_cfg, | 
 | 						      &map); | 
 | 	if (len < 0) | 
 | 		return len; | 
 |  | 
 | 	if (reg >= len) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return map[reg]; | 
 | } | 
 |  | 
 | static int cv1800_pconf_get(struct pinctrl_dev *pctldev, | 
 | 			    unsigned int pin_id, unsigned long *config) | 
 | { | 
 | 	struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); | 
 | 	int param = pinconf_to_config_param(*config); | 
 | 	struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); | 
 | 	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(pctrl, &pin->conf)); | 
 |  | 
 | 	switch (param) { | 
 | 	case PIN_CONFIG_BIAS_PULL_DOWN: | 
 | 		enabled = FIELD_GET(PIN_IO_PULLDOWN, value); | 
 | 		arg = cv1800_pull_down_typical_resistor(pctrl, pin); | 
 | 		break; | 
 | 	case PIN_CONFIG_BIAS_PULL_UP: | 
 | 		enabled = FIELD_GET(PIN_IO_PULLUP, value); | 
 | 		arg = cv1800_pull_up_typical_resistor(pctrl, pin); | 
 | 		break; | 
 | 	case PIN_CONFIG_DRIVE_STRENGTH_UA: | 
 | 		enabled = true; | 
 | 		arg = FIELD_GET(PIN_IO_DRIVE, value); | 
 | 		ret = cv1800_pinctrl_reg2oc(pctrl, pin, arg); | 
 | 		if (ret < 0) | 
 | 			return ret; | 
 | 		arg = ret; | 
 | 		break; | 
 | 	case PIN_CONFIG_INPUT_SCHMITT_UV: | 
 | 		arg = FIELD_GET(PIN_IO_SCHMITT, value); | 
 | 		ret = cv1800_pinctrl_reg2schmitt(pctrl, pin, 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 cv1800_pinctrl *pctrl, | 
 | 					 struct cv1800_pin *pin, | 
 | 					 unsigned long *configs, | 
 | 					 unsigned int num_configs, | 
 | 					 u32 *value) | 
 | { | 
 | 	int i; | 
 | 	u32 v = 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); | 
 | 			break; | 
 | 		case PIN_CONFIG_BIAS_PULL_UP: | 
 | 			v &= ~PIN_IO_PULLUP; | 
 | 			v |= FIELD_PREP(PIN_IO_PULLUP, arg); | 
 | 			break; | 
 | 		case PIN_CONFIG_DRIVE_STRENGTH_UA: | 
 | 			ret = cv1800_pinctrl_oc2reg(pctrl, pin, arg); | 
 | 			if (ret < 0) | 
 | 				return ret; | 
 | 			v &= ~PIN_IO_DRIVE; | 
 | 			v |= FIELD_PREP(PIN_IO_DRIVE, ret); | 
 | 			break; | 
 | 		case PIN_CONFIG_INPUT_SCHMITT_UV: | 
 | 			ret = cv1800_pinctrl_schmitt2reg(pctrl, pin, arg); | 
 | 			if (ret < 0) | 
 | 				return ret; | 
 | 			v &= ~PIN_IO_SCHMITT; | 
 | 			v |= FIELD_PREP(PIN_IO_SCHMITT, ret); | 
 | 			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); | 
 | 			break; | 
 | 		case PIN_CONFIG_BIAS_BUS_HOLD: | 
 | 			v &= ~PIN_IO_BUS_HOLD; | 
 | 			v |= FIELD_PREP(PIN_IO_BUS_HOLD, arg); | 
 | 			break; | 
 | 		default: | 
 | 			return -ENOTSUPP; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	*value = v; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cv1800_pin_set_config(struct cv1800_pinctrl *pctrl, | 
 | 				 unsigned int pin_id, | 
 | 				 u32 value) | 
 | { | 
 | 	struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); | 
 | 	unsigned long flags; | 
 | 	void __iomem *addr; | 
 |  | 
 | 	if (!pin) | 
 | 		return -EINVAL; | 
 |  | 
 | 	addr = cv1800_pinctrl_get_component_addr(pctrl, &pin->conf); | 
 |  | 
 | 	raw_spin_lock_irqsave(&pctrl->lock, flags); | 
 | 	writel(value, addr); | 
 | 	raw_spin_unlock_irqrestore(&pctrl->lock, flags); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cv1800_pconf_set(struct pinctrl_dev *pctldev, | 
 | 			    unsigned int pin_id, unsigned long *configs, | 
 | 			    unsigned int num_configs) | 
 | { | 
 | 	struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); | 
 | 	struct cv1800_pin *pin = cv1800_get_pin(pctrl, pin_id); | 
 | 	u32 value; | 
 |  | 
 | 	if (!pin) | 
 | 		return -ENODEV; | 
 |  | 
 | 	if (cv1800_pinconf_compute_config(pctrl, pin, | 
 | 					  configs, num_configs, &value)) | 
 | 		return -ENOTSUPP; | 
 |  | 
 | 	return cv1800_pin_set_config(pctrl, pin_id, value); | 
 | } | 
 |  | 
 | static int cv1800_pconf_group_set(struct pinctrl_dev *pctldev, | 
 | 				  unsigned int gsel, | 
 | 				  unsigned long *configs, | 
 | 				  unsigned int num_configs) | 
 | { | 
 | 	struct cv1800_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); | 
 | 	const struct group_desc *group; | 
 | 	const struct cv1800_pin_mux_config *pinmuxs; | 
 | 	u32 value; | 
 | 	int i; | 
 |  | 
 | 	group = pinctrl_generic_get_group(pctldev, gsel); | 
 | 	if (!group) | 
 | 		return -EINVAL; | 
 |  | 
 | 	pinmuxs = group->data; | 
 |  | 
 | 	if (cv1800_pinconf_compute_config(pctrl, pinmuxs[0].pin, | 
 | 					  configs, num_configs, &value)) | 
 | 		return -ENOTSUPP; | 
 |  | 
 | 	for (i = 0; i < group->grp.npins; i++) | 
 | 		cv1800_pin_set_config(pctrl, group->grp.pins[i], value); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct pinconf_ops cv1800_pconf_ops = { | 
 | 	.pin_config_get			= cv1800_pconf_get, | 
 | 	.pin_config_set			= cv1800_pconf_set, | 
 | 	.pin_config_group_set		= cv1800_pconf_group_set, | 
 | 	.is_generic			= true, | 
 | }; | 
 |  | 
 | int cv1800_pinctrl_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct cv1800_pinctrl *pctrl; | 
 | 	const struct cv1800_pinctrl_data *pctrl_data; | 
 | 	int ret; | 
 |  | 
 | 	pctrl_data = device_get_match_data(dev); | 
 | 	if (!pctrl_data) | 
 | 		return -ENODEV; | 
 |  | 
 | 	if (pctrl_data->npins == 0 || pctrl_data->npd == 0) | 
 | 		return dev_err_probe(dev, -EINVAL, "invalid pin data\n"); | 
 |  | 
 | 	pctrl = devm_kzalloc(dev, sizeof(*pctrl), GFP_KERNEL); | 
 | 	if (!pctrl) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	pctrl->power_cfg = devm_kcalloc(dev, pctrl_data->npd, | 
 | 					sizeof(u32), GFP_KERNEL); | 
 | 	if (!pctrl->power_cfg) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	pctrl->regs[0] = devm_platform_ioremap_resource_byname(pdev, "sys"); | 
 | 	if (IS_ERR(pctrl->regs[0])) | 
 | 		return PTR_ERR(pctrl->regs[0]); | 
 |  | 
 | 	pctrl->regs[1] = devm_platform_ioremap_resource_byname(pdev, "rtc"); | 
 | 	if (IS_ERR(pctrl->regs[1])) | 
 | 		return PTR_ERR(pctrl->regs[1]); | 
 |  | 
 | 	pctrl->pdesc.name = dev_name(dev); | 
 | 	pctrl->pdesc.pins = pctrl_data->pins; | 
 | 	pctrl->pdesc.npins = pctrl_data->npins; | 
 | 	pctrl->pdesc.pctlops = &cv1800_pctrl_ops; | 
 | 	pctrl->pdesc.pmxops = &cv1800_pmx_ops; | 
 | 	pctrl->pdesc.confops = &cv1800_pconf_ops; | 
 | 	pctrl->pdesc.owner = THIS_MODULE; | 
 |  | 
 | 	pctrl->data = pctrl_data; | 
 | 	pctrl->dev = dev; | 
 | 	raw_spin_lock_init(&pctrl->lock); | 
 | 	mutex_init(&pctrl->mutex); | 
 |  | 
 | 	platform_set_drvdata(pdev, pctrl); | 
 |  | 
 | 	ret = devm_pinctrl_register_and_init(dev, &pctrl->pdesc, | 
 | 					     pctrl, &pctrl->pctl_dev); | 
 | 	if (ret) | 
 | 		return dev_err_probe(dev, ret, | 
 | 				     "fail to register pinctrl driver\n"); | 
 |  | 
 | 	return pinctrl_enable(pctrl->pctl_dev); | 
 | } | 
 | EXPORT_SYMBOL_GPL(cv1800_pinctrl_probe); |