Link: https://lore.kernel.org/r/20260217200002.683975158@linuxfoundation.org Tested-by: Florian Fainelli <florian.fainelli@broadcom.com> Tested-by: Takeshi Ogasawara <takeshi.ogasawara@futuring-girl.com> Tested-by: Peter Schneider <pschneider1968@googlemail.com> Tested-by: Jon Hunter <jonathanh@nvidia.com> Tested-by: Salvatore Bonaccorso <carnil@debian.org> Tested-by: Brett A C Sheffield <bacs@librecast.net> Tested-by: Mark Brown <broonie@kernel.org> Tested-by: Luna Jernberg <droidbittin@gmail.com> Tested-by: Ronald Warsow <rwarsow@gmx.de> Tested-by: Justin M. Forbes <jforbes@fedoraproject.org> Tested-by: Ron Economos <re@w6rz.net> Tested-by: Miguel Ojeda <ojeda@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
588 lines
14 KiB
C
588 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
//
|
|
// Author: Jerry Zhu <Jerry.Zhu@cixtech.com>
|
|
// Author: Gary Yang <gary.yang@cixtech.com>
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/pinctrl/pinconf.h>
|
|
#include <linux/pinctrl/pinconf-generic.h>
|
|
#include <linux/pinctrl/pinctrl.h>
|
|
#include <linux/pinctrl/pinmux.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "../core.h"
|
|
#include "../pinconf.h"
|
|
#include "../pinctrl-utils.h"
|
|
#include "../pinmux.h"
|
|
#include "pinctrl-sky1.h"
|
|
|
|
#define SKY1_PIN_SIZE (4)
|
|
#define SKY1_MUX_MASK GENMASK(8, 7)
|
|
#define SKY1_MUX_SHIFT (7)
|
|
#define SKY1_PULLCONF_MASK GENMASK(6, 5)
|
|
#define SKY1_PULLUP_BIT (6)
|
|
#define SKY1_PULLDN_BIT (5)
|
|
#define SKY1_DS_MASK GENMASK(3, 0)
|
|
|
|
#define CIX_PIN_NO_SHIFT (8)
|
|
#define CIX_PIN_FUN_MASK GENMASK(1, 0)
|
|
#define CIX_GET_PIN_NO(x) ((x) >> CIX_PIN_NO_SHIFT)
|
|
#define CIX_GET_PIN_FUNC(x) ((x) & CIX_PIN_FUN_MASK)
|
|
#define SKY1_DEFAULT_DS_VAL (4)
|
|
|
|
static const char * const sky1_gpio_functions[] = {
|
|
"func0", "func1", "func2", "func3",
|
|
};
|
|
|
|
static unsigned char sky1_ds_table[] = {
|
|
2, 3, 5, 6, 8, 9, 11, 12, 13, 14, 17, 18, 20, 21, 23, 24,
|
|
};
|
|
|
|
static bool sky1_pctrl_is_function_valid(struct sky1_pinctrl *spctl,
|
|
u32 pin_num, u32 fnum)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < spctl->info->npins; i++) {
|
|
const struct sky1_pin_desc *pin = spctl->info->pins + i;
|
|
|
|
if (pin->pin.number == pin_num) {
|
|
if (fnum < pin->nfunc)
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int sky1_pctrl_dt_node_to_map_func(struct sky1_pinctrl *spctl,
|
|
u32 pin, u32 fnum, struct sky1_pinctrl_group *grp,
|
|
struct pinctrl_map **map, unsigned int *reserved_maps,
|
|
unsigned int *num_maps)
|
|
{
|
|
bool ret;
|
|
|
|
if (*num_maps == *reserved_maps)
|
|
return -ENOSPC;
|
|
|
|
(*map)[*num_maps].type = PIN_MAP_TYPE_MUX_GROUP;
|
|
(*map)[*num_maps].data.mux.group = grp->name;
|
|
|
|
ret = sky1_pctrl_is_function_valid(spctl, pin, fnum);
|
|
if (!ret) {
|
|
dev_err(spctl->dev, "invalid function %d on pin %d .\n",
|
|
fnum, pin);
|
|
return -EINVAL;
|
|
}
|
|
|
|
(*map)[*num_maps].data.mux.function = sky1_gpio_functions[fnum];
|
|
(*num_maps)++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct sky1_pinctrl_group *
|
|
sky1_pctrl_find_group_by_pin(struct sky1_pinctrl *spctl, u32 pin)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < spctl->info->npins; i++) {
|
|
struct sky1_pinctrl_group *grp =
|
|
(struct sky1_pinctrl_group *)spctl->groups + i;
|
|
|
|
if (grp->pin == pin)
|
|
return grp;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int sky1_pctrl_dt_subnode_to_map(struct pinctrl_dev *pctldev,
|
|
struct device_node *node,
|
|
struct pinctrl_map **map,
|
|
unsigned int *reserved_maps,
|
|
unsigned int *num_maps)
|
|
{
|
|
struct property *pins;
|
|
u32 pinfunc, pin, func;
|
|
int num_pins, num_funcs, maps_per_pin;
|
|
unsigned long *configs;
|
|
unsigned int num_configs;
|
|
bool has_config = false;
|
|
int i, err;
|
|
unsigned int reserve = 0;
|
|
struct sky1_pinctrl_group *grp;
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
pins = of_find_property(node, "pinmux", NULL);
|
|
if (!pins) {
|
|
dev_err(spctl->dev, "missing pins property in node %pOFn .\n",
|
|
node);
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = pinconf_generic_parse_dt_config(node, pctldev, &configs,
|
|
&num_configs);
|
|
if (err)
|
|
return err;
|
|
|
|
if (num_configs)
|
|
has_config = true;
|
|
|
|
num_pins = pins->length / sizeof(u32);
|
|
num_funcs = num_pins;
|
|
maps_per_pin = 0;
|
|
if (num_funcs)
|
|
maps_per_pin++;
|
|
if (has_config && num_pins >= 1)
|
|
maps_per_pin++;
|
|
|
|
if (!num_pins || !maps_per_pin) {
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
reserve = num_pins * maps_per_pin;
|
|
|
|
err = pinctrl_utils_reserve_map(pctldev, map,
|
|
reserved_maps, num_maps, reserve);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
for (i = 0; i < num_pins; i++) {
|
|
err = of_property_read_u32_index(node, "pinmux",
|
|
i, &pinfunc);
|
|
if (err)
|
|
goto exit;
|
|
|
|
pin = CIX_GET_PIN_NO(pinfunc);
|
|
func = CIX_GET_PIN_FUNC(pinfunc);
|
|
pctldev->num_functions = ARRAY_SIZE(sky1_gpio_functions);
|
|
|
|
if (pin >= pctldev->desc->npins ||
|
|
func >= pctldev->num_functions) {
|
|
dev_err(spctl->dev, "invalid pins value.\n");
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
grp = sky1_pctrl_find_group_by_pin(spctl, pin);
|
|
if (!grp) {
|
|
dev_err(spctl->dev, "unable to match pin %d to group\n",
|
|
pin);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
err = sky1_pctrl_dt_node_to_map_func(spctl, pin, func, grp,
|
|
map, reserved_maps, num_maps);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
if (has_config) {
|
|
err = pinctrl_utils_add_map_configs(pctldev, map,
|
|
reserved_maps, num_maps, grp->name,
|
|
configs, num_configs,
|
|
PIN_MAP_TYPE_CONFIGS_GROUP);
|
|
if (err < 0)
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
err = 0;
|
|
|
|
exit:
|
|
kfree(configs);
|
|
return err;
|
|
}
|
|
|
|
static int sky1_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev,
|
|
struct device_node *np_config,
|
|
struct pinctrl_map **map, unsigned int *num_maps)
|
|
{
|
|
unsigned int reserved_maps;
|
|
int ret;
|
|
|
|
*map = NULL;
|
|
*num_maps = 0;
|
|
reserved_maps = 0;
|
|
|
|
for_each_child_of_node_scoped(np_config, np) {
|
|
ret = sky1_pctrl_dt_subnode_to_map(pctldev, np, map,
|
|
&reserved_maps, num_maps);
|
|
if (ret < 0) {
|
|
pinctrl_utils_free_map(pctldev, *map, *num_maps);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sky1_dt_free_map(struct pinctrl_dev *pctldev,
|
|
struct pinctrl_map *map,
|
|
unsigned int num_maps)
|
|
{
|
|
kfree(map);
|
|
}
|
|
|
|
static int sky1_pctrl_get_groups_count(struct pinctrl_dev *pctldev)
|
|
{
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
return spctl->info->npins;
|
|
}
|
|
|
|
static const char *sky1_pctrl_get_group_name(struct pinctrl_dev *pctldev,
|
|
unsigned int group)
|
|
{
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
return spctl->groups[group].name;
|
|
}
|
|
|
|
static int sky1_pctrl_get_group_pins(struct pinctrl_dev *pctldev,
|
|
unsigned int group,
|
|
const unsigned int **pins,
|
|
unsigned int *num_pins)
|
|
{
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
*pins = (unsigned int *)&spctl->groups[group].pin;
|
|
*num_pins = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sky1_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s,
|
|
unsigned int offset)
|
|
{
|
|
seq_printf(s, "%s", dev_name(pctldev->dev));
|
|
}
|
|
|
|
static const struct pinctrl_ops sky1_pctrl_ops = {
|
|
.dt_node_to_map = sky1_pctrl_dt_node_to_map,
|
|
.dt_free_map = sky1_dt_free_map,
|
|
.get_groups_count = sky1_pctrl_get_groups_count,
|
|
.get_group_name = sky1_pctrl_get_group_name,
|
|
.get_group_pins = sky1_pctrl_get_group_pins,
|
|
.pin_dbg_show = sky1_pin_dbg_show,
|
|
};
|
|
|
|
static int sky1_pmx_set_one_pin(struct sky1_pinctrl *spctl,
|
|
unsigned int pin, unsigned char muxval)
|
|
{
|
|
u32 reg_val;
|
|
void __iomem *pin_reg;
|
|
|
|
pin_reg = spctl->base + pin * SKY1_PIN_SIZE;
|
|
reg_val = readl(pin_reg);
|
|
reg_val &= ~SKY1_MUX_MASK;
|
|
reg_val |= muxval << SKY1_MUX_SHIFT;
|
|
writel(reg_val, pin_reg);
|
|
|
|
dev_dbg(spctl->dev, "write: offset 0x%x val 0x%x\n",
|
|
pin * SKY1_PIN_SIZE, reg_val);
|
|
return 0;
|
|
}
|
|
|
|
static int sky1_pmx_set_mux(struct pinctrl_dev *pctldev,
|
|
unsigned int function,
|
|
unsigned int group)
|
|
{
|
|
bool ret;
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sky1_pinctrl_group *g =
|
|
(struct sky1_pinctrl_group *)spctl->groups + group;
|
|
|
|
ret = sky1_pctrl_is_function_valid(spctl, g->pin, function);
|
|
if (!ret) {
|
|
dev_err(spctl->dev, "invalid function %d on group %d .\n",
|
|
function, group);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sky1_pmx_set_one_pin(spctl, g->pin, function);
|
|
return 0;
|
|
}
|
|
|
|
static int sky1_pmx_get_funcs_cnt(struct pinctrl_dev *pctldev)
|
|
{
|
|
return ARRAY_SIZE(sky1_gpio_functions);
|
|
}
|
|
|
|
static const char *sky1_pmx_get_func_name(struct pinctrl_dev *pctldev,
|
|
unsigned int selector)
|
|
{
|
|
return sky1_gpio_functions[selector];
|
|
}
|
|
|
|
static int sky1_pmx_get_func_groups(struct pinctrl_dev *pctldev,
|
|
unsigned int function,
|
|
const char * const **groups,
|
|
unsigned int * const num_groups)
|
|
{
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
const struct sky1_pinctrl_soc_info *info = spctl->info;
|
|
|
|
*groups = spctl->grp_names;
|
|
*num_groups = info->npins;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pinmux_ops sky1_pmx_ops = {
|
|
.get_functions_count = sky1_pmx_get_funcs_cnt,
|
|
.get_function_groups = sky1_pmx_get_func_groups,
|
|
.get_function_name = sky1_pmx_get_func_name,
|
|
.set_mux = sky1_pmx_set_mux,
|
|
};
|
|
|
|
static int sky1_pconf_set_pull_select(struct sky1_pinctrl *spctl,
|
|
unsigned int pin, bool enable, bool isup)
|
|
{
|
|
u32 reg_val, reg_pullsel = 0;
|
|
void __iomem *pin_reg;
|
|
|
|
pin_reg = spctl->base + pin * SKY1_PIN_SIZE;
|
|
reg_val = readl(pin_reg);
|
|
reg_val &= ~SKY1_PULLCONF_MASK;
|
|
|
|
if (!enable)
|
|
goto update;
|
|
|
|
if (isup)
|
|
reg_pullsel = BIT(SKY1_PULLUP_BIT);
|
|
else
|
|
reg_pullsel = BIT(SKY1_PULLDN_BIT);
|
|
|
|
update:
|
|
reg_val |= reg_pullsel;
|
|
writel(reg_val, pin_reg);
|
|
|
|
dev_dbg(spctl->dev, "write: offset 0x%x val 0x%x\n",
|
|
pin * SKY1_PIN_SIZE, reg_val);
|
|
return 0;
|
|
}
|
|
|
|
static int sky1_ds_to_index(unsigned char driving)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(sky1_ds_table); i++)
|
|
if (driving == sky1_ds_table[i])
|
|
return i;
|
|
return SKY1_DEFAULT_DS_VAL;
|
|
}
|
|
|
|
static int sky1_pconf_set_driving(struct sky1_pinctrl *spctl,
|
|
unsigned int pin, unsigned char driving)
|
|
{
|
|
unsigned int reg_val, val;
|
|
void __iomem *pin_reg;
|
|
|
|
if (pin >= spctl->info->npins)
|
|
return -EINVAL;
|
|
|
|
pin_reg = spctl->base + pin * SKY1_PIN_SIZE;
|
|
reg_val = readl(pin_reg);
|
|
reg_val &= ~SKY1_DS_MASK;
|
|
val = sky1_ds_to_index(driving);
|
|
reg_val |= (val & SKY1_DS_MASK);
|
|
writel(reg_val, pin_reg);
|
|
|
|
dev_dbg(spctl->dev, "write: offset 0x%x val 0x%x\n",
|
|
pin * SKY1_PIN_SIZE, reg_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sky1_pconf_parse_conf(struct pinctrl_dev *pctldev,
|
|
unsigned int pin, enum pin_config_param param,
|
|
enum pin_config_param arg)
|
|
{
|
|
int ret = 0;
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
|
|
switch (param) {
|
|
case PIN_CONFIG_BIAS_DISABLE:
|
|
ret = sky1_pconf_set_pull_select(spctl, pin, false, false);
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_UP:
|
|
ret = sky1_pconf_set_pull_select(spctl, pin, true, true);
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_DOWN:
|
|
ret = sky1_pconf_set_pull_select(spctl, pin, true, false);
|
|
break;
|
|
case PIN_CONFIG_DRIVE_STRENGTH:
|
|
ret = sky1_pconf_set_driving(spctl, pin, arg);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sky1_pconf_group_get(struct pinctrl_dev *pctldev,
|
|
unsigned int group,
|
|
unsigned long *config)
|
|
{
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sky1_pinctrl_group *g = &spctl->groups[group];
|
|
|
|
*config = g->config;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sky1_pconf_group_set(struct pinctrl_dev *pctldev, unsigned int group,
|
|
unsigned long *configs, unsigned int num_configs)
|
|
{
|
|
struct sky1_pinctrl *spctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sky1_pinctrl_group *g = &spctl->groups[group];
|
|
int i, ret;
|
|
|
|
for (i = 0; i < num_configs; i++) {
|
|
ret = sky1_pconf_parse_conf(pctldev, g->pin,
|
|
pinconf_to_config_param(configs[i]),
|
|
pinconf_to_config_argument(configs[i]));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
g->config = configs[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pinconf_ops sky1_pinconf_ops = {
|
|
.pin_config_group_get = sky1_pconf_group_get,
|
|
.pin_config_group_set = sky1_pconf_group_set,
|
|
};
|
|
|
|
static int sky1_pctrl_build_state(struct platform_device *pdev)
|
|
{
|
|
struct sky1_pinctrl *spctl = platform_get_drvdata(pdev);
|
|
const struct sky1_pinctrl_soc_info *info = spctl->info;
|
|
int i;
|
|
|
|
/* Allocate groups */
|
|
spctl->groups = devm_kcalloc(&pdev->dev, info->npins,
|
|
sizeof(*spctl->groups), GFP_KERNEL);
|
|
if (!spctl->groups)
|
|
return -ENOMEM;
|
|
|
|
/* We assume that one pin is one group, use pin name as group name. */
|
|
spctl->grp_names = devm_kcalloc(&pdev->dev, info->npins,
|
|
sizeof(*spctl->grp_names), GFP_KERNEL);
|
|
if (!spctl->grp_names)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < info->npins; i++) {
|
|
const struct sky1_pin_desc *pin = spctl->info->pins + i;
|
|
struct sky1_pinctrl_group *group =
|
|
(struct sky1_pinctrl_group *)spctl->groups + i;
|
|
|
|
group->name = pin->pin.name;
|
|
group->pin = pin->pin.number;
|
|
spctl->grp_names[i] = pin->pin.name;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sky1_base_pinctrl_probe(struct platform_device *pdev,
|
|
const struct sky1_pinctrl_soc_info *info)
|
|
{
|
|
struct pinctrl_desc *sky1_pinctrl_desc;
|
|
struct sky1_pinctrl *spctl;
|
|
struct pinctrl_pin_desc *pins;
|
|
int ret, i;
|
|
|
|
if (!info || !info->pins || !info->npins) {
|
|
dev_err(&pdev->dev, "wrong pinctrl info\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Create state holders etc for this driver */
|
|
spctl = devm_kzalloc(&pdev->dev, sizeof(*spctl), GFP_KERNEL);
|
|
if (!spctl)
|
|
return -ENOMEM;
|
|
|
|
spctl->info = info;
|
|
platform_set_drvdata(pdev, spctl);
|
|
|
|
spctl->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(spctl->base))
|
|
return PTR_ERR(spctl->base);
|
|
|
|
sky1_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*sky1_pinctrl_desc),
|
|
GFP_KERNEL);
|
|
if (!sky1_pinctrl_desc)
|
|
return -ENOMEM;
|
|
|
|
pins = devm_kcalloc(&pdev->dev, info->npins, sizeof(*pins),
|
|
GFP_KERNEL);
|
|
if (!pins)
|
|
return -ENOMEM;
|
|
for (i = 0; i < info->npins; i++)
|
|
pins[i] = info->pins[i].pin;
|
|
|
|
ret = sky1_pctrl_build_state(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
sky1_pinctrl_desc->name = dev_name(&pdev->dev);
|
|
sky1_pinctrl_desc->pins = pins;
|
|
sky1_pinctrl_desc->npins = info->npins;
|
|
sky1_pinctrl_desc->pctlops = &sky1_pctrl_ops;
|
|
sky1_pinctrl_desc->pmxops = &sky1_pmx_ops;
|
|
sky1_pinctrl_desc->confops = &sky1_pinconf_ops;
|
|
sky1_pinctrl_desc->owner = THIS_MODULE;
|
|
spctl->dev = &pdev->dev;
|
|
ret = devm_pinctrl_register_and_init(&pdev->dev,
|
|
sky1_pinctrl_desc, spctl,
|
|
&spctl->pctl);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "could not register SKY1 pinctrl driver\n");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The SKY1 SoC has two pin controllers: one for normal working state
|
|
* and one for sleep state. Since one controller only has working
|
|
* states and the other only sleep states, it will seem to the
|
|
* controller is always in the first configured state, so no
|
|
* transitions between default->sleep->default are detected and no
|
|
* new pin states are applied when we go in and out of sleep state.
|
|
*
|
|
* To counter this, provide dummies, so that the sleep-only pin
|
|
* controller still get some default states, and the working state pin
|
|
* controller get some sleep states, so that state transitions occur
|
|
* and we re-configure pins for default and sleep states.
|
|
*/
|
|
pinctrl_provide_dummies();
|
|
|
|
dev_dbg(&pdev->dev, "initialized SKY1 pinctrl driver\n");
|
|
|
|
return pinctrl_enable(spctl->pctl);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sky1_base_pinctrl_probe);
|
|
|
|
|
|
MODULE_AUTHOR("Jerry Zhu <Jerry.Zhu@cixtech.com>");
|
|
MODULE_DESCRIPTION("Cix SKy1 pinctrl base driver");
|
|
MODULE_LICENSE("GPL");
|