linux/drivers/pinctrl/sophgo/pinctrl-cv18xx.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Sophgo CV18XX SoCs pinctrl driver.
 *
 * Copyright (C) 2024 Inochi Amaoto <[email protected]>
 *
 */

#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 = devm_kcalloc(dev, 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);