mbox series

[RESEND,0/3] Add initial support for Canaan Kendryte K230 pinctrl

Message ID 20240916063021.311721-1-18771902331@163.com
Headers show
Series Add initial support for Canaan Kendryte K230 pinctrl | expand

Message

Ze Huang Sept. 16, 2024, 6:30 a.m. UTC
Resend the patches to the same thread and fix incorrect patch ID.

This patch series introduces support for the pinctrl driver of the Canaan
K230 SoC. The K230 SoC features 64 IO pins, each of which can be configured
for up to five different functions.

The controller manages the entire pin configuration and multiplexing
through a single register, which control features such as schmitt trigger,
drive strength, bias pull-up/down, input/output enable, power source, and
mux mode.

The changes have been tested on the K230 development board.

The pin function definition can be found here [1], and most of the DTS data
was converted from the vendor's code [2].

Link: https://developer.canaan-creative.com/k230/dev/_downloads/a53655a81951bc8a440ae647be286e75/K230_PINOUT_V1.1_20230321.xlsx [1]
Link: https://github.com/kendryte/k230_sdk/blob/main/src/little/uboot/arch/riscv/dts/k230_canmv.dts [2]

Ze Huang (3):
  dt-bindings: pinctrl: Add support for canaan,k230 SoC
  pinctrl: canaan: Add support for k230 SoC
  riscv: dts: canaan: Add k230's pinctrl node

 .../bindings/pinctrl/canaan,k230-pinctrl.yaml | 128 ++++
 arch/riscv/boot/dts/canaan/k230-pinctrl.dtsi  | 316 ++++++++
 arch/riscv/boot/dts/canaan/k230-pinctrl.h     |  18 +
 arch/riscv/boot/dts/canaan/k230.dtsi          |   2 +
 drivers/pinctrl/Kconfig                       |  10 +
 drivers/pinctrl/Makefile                      |   1 +
 drivers/pinctrl/pinctrl-k230.c                | 674 ++++++++++++++++++
 7 files changed, 1149 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pinctrl/canaan,k230-pinctrl.yaml
 create mode 100644 arch/riscv/boot/dts/canaan/k230-pinctrl.dtsi
 create mode 100644 arch/riscv/boot/dts/canaan/k230-pinctrl.h
 create mode 100644 drivers/pinctrl/pinctrl-k230.c

base-commit: 0eea987088a22d73d81e968de7347cdc7e594f72
prerequisite-patch-id: 2401703b57448c9ea2c3dc7650b4502491a28944
prerequisite-patch-id: 50ccf1104191cdf22f9077880d3dc781b190a3c8
prerequisite-patch-id: f8b983b301d0c14f1448b9e4c321262a509e061e
prerequisite-patch-id: ced4a01ccd8ddab2fd308d543ddf47bd1641518a
prerequisite-patch-id: c2144cf468c57b856830a61615ba6ba501e8ec58
prerequisite-patch-id: 704efc6e76814e1877748959d7319d558c8386c1

Comments

Ze Huang Sept. 18, 2024, 8:10 a.m. UTC | #1
On 9/16/24 11:58 PM, Krzysztof Kozlowski wrote:
> On 16/09/2024 08:47, Ze Huang wrote:
>> Configuration of the K230 is similar to that of the K210. However, in K210,
>> the 256 functions for each pin are shared, whereas in K230, multiplex
>> functions are different for every pin.
>>
>> Signed-off-by: Ze Huang <18771902331@163.com>
>> ---
>>   drivers/pinctrl/Kconfig        |  10 +
>>   drivers/pinctrl/Makefile       |   1 +
>>   drivers/pinctrl/pinctrl-k230.c | 674 +++++++++++++++++++++++++++++++++
>>   3 files changed, 685 insertions(+)
>>   create mode 100644 drivers/pinctrl/pinctrl-k230.c
> ...
>
>> +
>> +struct k230_pinctrl {
>> +	struct pinctrl_desc	pctl;
>> +	struct pinctrl_dev	*pctl_dev;
>> +	struct regmap		*regmap_base;
>> +	void __iomem		*base;
>> +	struct k230_pin_group	*groups;
>> +	unsigned int		ngroups;
>> +	struct k230_pmx_func	*functions;
>> +	unsigned int		nfunctions;
>> +};
>> +
>> +static struct regmap_config k230_regmap_config = {
> Why is this not a const?

Will move definition of 'name' here and set to const.

>
>> +	.reg_bits	= 32,
>> +	.val_bits	= 32,
>> +	.max_register	= 0x100,
>> +	.reg_stride	= 4,
>> +};
>> +
>> +static int k230_get_groups_count(struct pinctrl_dev *pctldev)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +
>> +	return info->ngroups;
>> +}
>> +
>> +static const char *k230_get_group_name(struct pinctrl_dev *pctldev,
>> +				       unsigned int selector)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +
>> +	return info->groups[selector].name;
>> +}
>> +
>> +static int k230_get_group_pins(struct pinctrl_dev *pctldev,
>> +			       unsigned int selector,
>> +			       const unsigned int **pins,
>> +			       unsigned int *num_pins)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +
>> +	if (selector >= info->ngroups)
>> +		return -EINVAL;
>> +
>> +	*pins = info->groups[selector].pins;
>> +	*num_pins = info->groups[selector].num_pins;
>> +
>> +	return 0;
>> +}
>> +
>> +static inline const struct k230_pmx_func *k230_name_to_funtion(
>> +		const struct k230_pinctrl *info, const char *name)
>> +{
>> +	unsigned int i;
>> +
>> +	for (i = 0; i < info->nfunctions; i++) {
>> +		if (!strcmp(info->functions[i].name, name))
>> +			return &info->functions[i];
>> +	}
>> +
>> +	return NULL;
>> +}
>> +
>> +static int k230_pinctrl_parse_groups(struct device_node *np,
>> +				     struct k230_pin_group *grp,
>> +				     struct k230_pinctrl *info,
>> +				     unsigned int index)
>> +{
>> +	struct device *dev = info->pctl_dev->dev;
>> +	const __be32 *list;
>> +	int size, i, ret;
>> +
>> +	grp->name = np->name;
>> +
>> +	list = of_get_property(np, "pinmux", &size);
>> +	size /= sizeof(*list);
>> +
>> +	grp->num_pins = size;
>> +	grp->pins = devm_kcalloc(dev, grp->num_pins, sizeof(*grp->pins),
>> +				 GFP_KERNEL);
>> +	grp->data = devm_kcalloc(dev, grp->num_pins, sizeof(*grp->data),
>> +				 GFP_KERNEL);
>> +	if (!grp->pins || !grp->data)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < size; i++) {
>> +		unsigned int mux_data = be32_to_cpu(*list++);
>> +
>> +		grp->pins[i] = (mux_data >> 8);
>> +		grp->data[i].func = (mux_data & 0xff);
>> +
>> +		ret = pinconf_generic_parse_dt_config(np, NULL,
>> +						      &grp->data[i].configs,
>> +						      &grp->data[i].nconfigs);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +	of_node_put(np);
>> +
> This looks like double free. There is no get in this scope.

Thanks for pointing that out. 'np' would be released by the caller. Will 
remove
`of_node_put()` here.

>> +	return 0;
>> +}
>> +
>> +static void k230_pinctrl_child_count(struct k230_pinctrl *info,
>> +				     struct device_node *np)
>> +{
>> +	struct device_node *child;
>> +
>> +	for_each_child_of_node(np, child) {
>> +		info->nfunctions++;
>> +		info->ngroups += of_get_child_count(child);
>> +	}
>> +}
>> +
>> +static int k230_pinctrl_parse_functions(struct device_node *np,
>> +					struct k230_pinctrl *info,
>> +					unsigned int index)
>> +{
>> +	struct device *dev = info->pctl_dev->dev;
>> +	struct k230_pmx_func *func;
>> +	struct k230_pin_group *grp;
>> +	struct device_node *child;
>> +	static unsigned int idx, i;
>> +	int ret;
>> +
>> +	func = &info->functions[index];
>> +
>> +	func->name = np->name;
>> +	func->ngroups = of_get_child_count(np);
>> +	if (func->ngroups <= 0)
>> +		return 0;
>> +
>> +	func->groups = devm_kcalloc(dev, func->ngroups,
>> +				    sizeof(*func->groups), GFP_KERNEL);
>> +	func->group_idx = devm_kcalloc(dev, func->ngroups,
>> +				       sizeof(*func->group_idx), GFP_KERNEL);
>> +	if (!func->groups || !func->group_idx)
>> +		return -ENOMEM;
>> +
>> +	i = 0;
>> +
>> +	for_each_child_of_node(np, child) {
>> +		func->groups[i] = child->name;
>> +		func->group_idx[i] = idx;
>> +		grp = &info->groups[idx];
>> +		idx++;
>> +		ret = k230_pinctrl_parse_groups(child, grp, info, i++);
>> +		if (ret) {
>> +			of_node_put(child);
> Use scoped loop instead.

OK, will use `for_each_child_of_node_scoped` instead.

>
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int k230_pinctrl_parse_dt(struct platform_device *pdev,
>> +				 struct k230_pinctrl *info)
> Please keep all probe related code next to each other. That's quite
> confusing to find probe code far away from the probe().

Noted, will do.

>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct device_node *np = dev->of_node;
>> +	struct device_node *child;
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	k230_pinctrl_child_count(info, np);
>> +
>> +	info->functions = devm_kcalloc(dev, info->nfunctions,
>> +				       sizeof(*info->functions), GFP_KERNEL);
>> +	info->groups = devm_kcalloc(dev, info->ngroups,
>> +				    sizeof(*info->groups), GFP_KERNEL);
>> +	if (!info->functions || !info->groups)
>> +		return -ENOMEM;
>> +
>> +	i = 0;
>> +
>> +	for_each_child_of_node(np, child) {
>> +		ret = k230_pinctrl_parse_functions(child, info, i++);
>> +		if (ret) {
>> +			dev_err(dev, "failed to parse function\n");
>> +			of_node_put(child);
> Use scoped loop instead.

Will use `for_each_child_of_node_scoped` instead.

>> +			return ret;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static struct pinctrl_pin_desc k230_pins[] = {
> Why is this not a const?

Field `drv_data` was used to point to currently activated group, which would
be updated in `set_mux()`. It was used to print the name of the current
function of pin in `k230_pinctrl_pin_dbg_show()`.

>> +	PINCTRL_PIN(0,  "IO0"),  PINCTRL_PIN(1,  "IO1"),  PINCTRL_PIN(2,  "IO2"),
>> +	PINCTRL_PIN(3,  "IO3"),  PINCTRL_PIN(4,  "IO4"),  PINCTRL_PIN(5,  "IO5"),
>> +	PINCTRL_PIN(6,  "IO6"),  PINCTRL_PIN(7,  "IO7"),  PINCTRL_PIN(8,  "IO8"),
>> +	PINCTRL_PIN(9,  "IO9"),  PINCTRL_PIN(10, "IO10"), PINCTRL_PIN(11, "IO11"),
>> +	PINCTRL_PIN(12, "IO12"), PINCTRL_PIN(13, "IO13"), PINCTRL_PIN(14, "IO14"),
>> +	PINCTRL_PIN(15, "IO15"), PINCTRL_PIN(16, "IO16"), PINCTRL_PIN(17, "IO17"),
>> +	PINCTRL_PIN(18, "IO18"), PINCTRL_PIN(19, "IO19"), PINCTRL_PIN(20, "IO20"),
>> +	PINCTRL_PIN(21, "IO21"), PINCTRL_PIN(22, "IO22"), PINCTRL_PIN(23, "IO23"),
>> +	PINCTRL_PIN(24, "IO24"), PINCTRL_PIN(25, "IO25"), PINCTRL_PIN(26, "IO26"),
>> +	PINCTRL_PIN(27, "IO27"), PINCTRL_PIN(28, "IO28"), PINCTRL_PIN(29, "IO29"),
>> +	PINCTRL_PIN(30, "IO30"), PINCTRL_PIN(31, "IO31"), PINCTRL_PIN(32, "IO32"),
>> +	PINCTRL_PIN(33, "IO33"), PINCTRL_PIN(34, "IO34"), PINCTRL_PIN(35, "IO35"),
>> +	PINCTRL_PIN(36, "IO36"), PINCTRL_PIN(37, "IO37"), PINCTRL_PIN(38, "IO38"),
>> +	PINCTRL_PIN(39, "IO39"), PINCTRL_PIN(40, "IO40"), PINCTRL_PIN(41, "IO41"),
>> +	PINCTRL_PIN(42, "IO42"), PINCTRL_PIN(43, "IO43"), PINCTRL_PIN(44, "IO44"),
>> +	PINCTRL_PIN(45, "IO45"), PINCTRL_PIN(46, "IO46"), PINCTRL_PIN(47, "IO47"),
>> +	PINCTRL_PIN(48, "IO48"), PINCTRL_PIN(49, "IO49"), PINCTRL_PIN(50, "IO50"),
>> +	PINCTRL_PIN(51, "IO51"), PINCTRL_PIN(52, "IO52"), PINCTRL_PIN(53, "IO53"),
>> +	PINCTRL_PIN(54, "IO54"), PINCTRL_PIN(55, "IO55"), PINCTRL_PIN(56, "IO56"),
>> +	PINCTRL_PIN(57, "IO57"), PINCTRL_PIN(58, "IO58"), PINCTRL_PIN(59, "IO59"),
>> +	PINCTRL_PIN(60, "IO60"), PINCTRL_PIN(61, "IO61"), PINCTRL_PIN(62, "IO62"),
>> +	PINCTRL_PIN(63, "IO63")
>> +};
>> +
>> +static void k230_pinctrl_pin_dbg_show(struct pinctrl_dev *pctldev,
>> +				      struct seq_file *s, unsigned int offset)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +	u32 val, mode, bias, drive, input, output, slew, schmitt, power;
>> +	struct k230_pin_group *grp = k230_pins[offset].drv_data;
>> +	static const char * const biasing[] = {
>> +			"pull none", "pull down", "pull up", "" };
>> +	static const char * const enable[] = {
>> +			"disable", "enable" };
>> +	static const char * const power_source[] = {
>> +			"3V3", "1V8" };
>> +	int ret;
>> +
>> +	ret = regmap_read(info->regmap_base, offset * 4, &val);
>> +	if (ret) {
>> +		dev_err(info->pctl_dev->dev,
>> +			"failed to read offset 0x%x\n", offset * 4);
>> +		return;
>> +	}
>> +
>> +	mode	= (val & K230_PC_SEL) >> K230_SHIFT_SEL;
>> +	drive	= (val & K230_PC_DS) >> K230_SHIFT_DS;
>> +	bias	= (val & K230_PC_BIAS) >> K230_SHIFT_BIAS;
>> +	input	= (val & K230_PC_IE) >> K230_SHIFT_IE;
>> +	output	= (val & K230_PC_OE) >> K230_SHIFT_OE;
>> +	slew	= (val & K230_PC_SL) >> K230_SHIFT_SL;
>> +	schmitt	= (val & K230_PC_ST) >> K230_SHIFT_ST;
>> +	power	= (val & K230_PC_MSC) >> K230_SHIFT_MSC;
>> +
>> +	seq_printf(s, "%s - strength %d - %s - %s - slewrate %s - schmitt %s - %s",
>> +		   grp ? grp->name : "unknown",
>> +		   drive,
>> +		   biasing[bias],
>> +		   input ? "input" : "output",
>> +		   enable[slew],
>> +		   enable[schmitt],
>> +		   power_source[power]);
>> +}
>> +
>> +static int k230_dt_node_to_map(struct pinctrl_dev *pctldev,
>> +			       struct device_node *np_config,
>> +			       struct pinctrl_map **map,
>> +			       unsigned int *num_maps)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +	struct device *dev = info->pctl_dev->dev;
>> +	const struct k230_pmx_func *func;
>> +	const struct k230_pin_group *grp;
>> +	struct pinctrl_map *new_map;
>> +	int map_num, i, j, idx;
>> +	unsigned int grp_id;
>> +
>> +	func = k230_name_to_funtion(info, np_config->name);
>> +	if (!func) {
>> +		dev_err(dev, "function %s not found\n", np_config->name);
>> +		return -EINVAL;
>> +	}
>> +
>> +	map_num = 0;
>> +	for (i = 0; i < func->ngroups; ++i) {
>> +		grp_id = func->group_idx[i];
>> +		/* npins of config map plus a mux map */
>> +		map_num += info->groups[grp_id].num_pins + 1;
>> +	}
>> +
>> +	new_map = kcalloc(map_num, sizeof(*new_map), GFP_KERNEL);
>> +	if (!new_map)
>> +		return -ENOMEM;
>> +	*map = new_map;
>> +	*num_maps = map_num;
>> +
>> +	idx = 0;
>> +	for (i = 0; i < func->ngroups; ++i) {
>> +		grp_id = func->group_idx[i];
>> +		grp = &info->groups[grp_id];
>> +		new_map[idx].type = PIN_MAP_TYPE_MUX_GROUP;
>> +		new_map[idx].data.mux.group = grp->name;
>> +		new_map[idx].data.mux.function = np_config->name;
>> +		idx++;
>> +
>> +		for (j = 0; j < grp->num_pins; ++j) {
>> +			new_map[idx].type = PIN_MAP_TYPE_CONFIGS_PIN;
>> +			new_map[idx].data.configs.group_or_pin =
>> +				pin_get_name(pctldev, grp->pins[j]);
>> +			new_map[idx].data.configs.configs =
>> +				grp->data[j].configs;
>> +			new_map[idx].data.configs.num_configs =
>> +				grp->data[j].nconfigs;
>> +			idx++;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +}
> ...
>
>> +
>> +	ret = regmap_write(info->regmap_base, pin * 4, val);
>> +	if (ret) {
>> +		dev_err(dev, "failed to write offset 0x%x\n", pin * 4);
> Isn't regmap an MMIO? If so, drop all of such messages. This just makes
> unlikely error paths too big.

Acknowledged. Will drop them.

>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int k230_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
>> +			    unsigned long *configs, unsigned int num_configs)
>> +{
>> +	enum pin_config_param param;
>> +	unsigned int arg, i;
>> +	int ret;
>> +
>> +	if (WARN_ON(pin >= K230_NPINS))
> Drop WARN_ON. No need to panic kernel. Instead, handle correctly the error.

Acknowledged. Will use dev_err instead.

>> +		return -EINVAL;
>> +
>> +	for (i = 0; i < num_configs; i++) {
>> +		param = pinconf_to_config_param(configs[i]);
>> +		arg = pinconf_to_config_argument(configs[i]);
>> +		ret = k230_pinconf_set_param(pctldev, pin, param, arg);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void k230_pconf_dbg_show(struct pinctrl_dev *pctldev,
>> +				struct seq_file *s, unsigned int pin)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +	unsigned int val;
>> +	int ret;
>> +
>> +	ret = regmap_read(info->regmap_base, pin * 4, &val);
>> +	if (ret) {
>> +		dev_err(info->pctl_dev->dev, "failed to read offset 0x%x\n", pin * 4);
>> +		return;
>> +	}
>> +
>> +	seq_printf(s, " 0x%08x", val);
>> +}
>> +
>> +static const struct pinconf_ops k230_pinconf_ops = {
>> +	.is_generic		= true,
>> +	.pin_config_get		= k230_pinconf_get,
>> +	.pin_config_set		= k230_pinconf_set,
>> +	.pin_config_dbg_show	= k230_pconf_dbg_show,
>> +};
>> +
>> +static int k230_get_functions_count(struct pinctrl_dev *pctldev)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +
>> +	return info->nfunctions;
>> +}
>> +
>> +static const char *k230_get_fname(struct pinctrl_dev *pctldev,
>> +				  unsigned int selector)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +
>> +	return info->functions[selector].name;
>> +}
>> +
>> +static int k230_get_groups(struct pinctrl_dev *pctldev, unsigned int selector,
>> +			   const char * const **groups, unsigned int *num_groups)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +
>> +	*groups = info->functions[selector].groups;
>> +	*num_groups = info->functions[selector].ngroups;
>> +
>> +	return 0;
>> +}
>> +
>> +static int k230_set_mux(struct pinctrl_dev *pctldev, unsigned int selector,
>> +			unsigned int group)
>> +{
>> +	struct k230_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
>> +	const struct k230_pin_conf *data = info->groups[group].data;
>> +	struct k230_pin_group *grp = &info->groups[group];
>> +	const unsigned int *pins = grp->pins;
>> +	struct regmap *regmap;
>> +	unsigned int value, mask;
>> +	int cnt, reg;
>> +
>> +	regmap = info->regmap_base;
>> +
>> +	for (cnt = 0; cnt < grp->num_pins; cnt++) {
>> +		reg = pins[cnt] * 4;
>> +		value = data[cnt].func << K230_SHIFT_SEL;
>> +		mask = K230_PC_SEL;
>> +		regmap_update_bits(regmap, reg, mask, value);
>> +		k230_pins[pins[cnt]].drv_data = grp;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct pinmux_ops k230_pmxops = {
>> +	.get_functions_count	= k230_get_functions_count,
>> +	.get_function_name	= k230_get_fname,
>> +	.get_function_groups	= k230_get_groups,
>> +	.set_mux		= k230_set_mux,
>> +	.strict			= true,
>> +};
>> +
>> +static int k230_pinctrl_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct k230_pinctrl *info;
>> +	struct pinctrl_desc *pctl;
>> +
>> +	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
>> +	if (!info)
>> +		return -ENOMEM;
>> +
>> +	pctl = &info->pctl;
>> +
>> +	pctl->name	= "k230-pinctrl";
>> +	pctl->owner	= THIS_MODULE;
>> +	pctl->pins	= k230_pins;
>> +	pctl->npins	= ARRAY_SIZE(k230_pins);
>> +	pctl->pctlops	= &k230_pctrl_ops;
>> +	pctl->pmxops	= &k230_pmxops;
>> +	pctl->confops	= &k230_pinconf_ops;
>> +
>> +	info->base = devm_platform_ioremap_resource(pdev, 0);
>> +	if (IS_ERR(info->base))
>> +		return PTR_ERR(info->base);
>> +
>> +	k230_regmap_config.name = "canaan,pinctrl";
> Why this is not part of definition?

Will move to definition and set regmap to const.

>> +	info->regmap_base = devm_regmap_init_mmio(dev, info->base,
>> +						  &k230_regmap_config);
>> +	if (IS_ERR(info->regmap_base))
>> +		return dev_err_probe(dev, PTR_ERR(info->regmap_base),
>> +				     "failed to init regmap\n");
>> +
>> +	info->pctl_dev = devm_pinctrl_register(dev, pctl, info);
>> +	if (IS_ERR(info->pctl_dev))
>> +		return dev_err_probe(dev, PTR_ERR(info->pctl_dev),
>> +				     "devm_pinctrl_register failed\n");
>> +
>> +	k230_pinctrl_parse_dt(pdev, info);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id k230_dt_ids[] = {
>> +	{ .compatible = "canaan,k230-pinctrl", },
>> +	{ /* sintenel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, k230_dt_ids);
>> +
>> +static struct platform_driver k230_pinctrl_driver = {
>> +	.probe = k230_pinctrl_probe,
>> +	.driver = {
>> +		.name = "k230-pinctrl",
>> +		.of_match_table = k230_dt_ids,
>> +	},
>> +};
>> +module_platform_driver(k230_pinctrl_driver);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_AUTHOR("Ze Huang <18771902331@163.com>");
>> +MODULE_DESCRIPTION("Canaan K230 pinctrl driver");
> Best regards,
> Krzysztof