mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
8.0 KiB
305 lines
8.0 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (c) 2020 TOSHIBA CORPORATION |
|
* Copyright (c) 2020 Toshiba Electronic Devices & Storage Corporation |
|
* Copyright (c) 2020 Nobuhiro Iwamatsu <[email protected]> |
|
*/ |
|
|
|
#include <linux/init.h> |
|
#include <linux/of.h> |
|
#include <linux/io.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/pinctrl/pinctrl.h> |
|
#include <linux/pinctrl/pinmux.h> |
|
#include <linux/pinctrl/pinconf.h> |
|
#include <linux/pinctrl/pinconf-generic.h> |
|
#include "pinctrl-common.h" |
|
#include "../core.h" |
|
#include "../pinconf.h" |
|
#include "../pinctrl-utils.h" |
|
|
|
#define DSEL_MASK GENMASK(3, 0) |
|
|
|
/* private data */ |
|
struct visconti_pinctrl { |
|
void __iomem *base; |
|
struct device *dev; |
|
struct pinctrl_dev *pctl; |
|
struct pinctrl_desc pctl_desc; |
|
|
|
const struct visconti_pinctrl_devdata *devdata; |
|
|
|
spinlock_t lock; /* protect pinctrl register */ |
|
}; |
|
|
|
/* pinconf */ |
|
static int visconti_pin_config_set(struct pinctrl_dev *pctldev, |
|
unsigned int _pin, |
|
unsigned long *configs, |
|
unsigned int num_configs) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
const struct visconti_desc_pin *pin = &priv->devdata->pins[_pin]; |
|
enum pin_config_param param; |
|
unsigned int arg; |
|
int i, ret = 0; |
|
unsigned int val, set_val, pude_val; |
|
unsigned long flags; |
|
|
|
dev_dbg(priv->dev, "%s: pin = %d (%s)\n", __func__, _pin, pin->pin.name); |
|
|
|
spin_lock_irqsave(&priv->lock, flags); |
|
|
|
for (i = 0; i < num_configs; i++) { |
|
set_val = 0; |
|
pude_val = 0; |
|
|
|
param = pinconf_to_config_param(configs[i]); |
|
switch (param) { |
|
case PIN_CONFIG_BIAS_PULL_UP: |
|
set_val = 1; |
|
fallthrough; |
|
case PIN_CONFIG_BIAS_PULL_DOWN: |
|
/* update pudsel setting */ |
|
val = readl(priv->base + pin->pudsel_offset); |
|
val &= ~BIT(pin->pud_shift); |
|
val |= set_val << pin->pud_shift; |
|
writel(val, priv->base + pin->pudsel_offset); |
|
pude_val = 1; |
|
fallthrough; |
|
case PIN_CONFIG_BIAS_DISABLE: |
|
/* update pude setting */ |
|
val = readl(priv->base + pin->pude_offset); |
|
val &= ~BIT(pin->pud_shift); |
|
val |= pude_val << pin->pud_shift; |
|
writel(val, priv->base + pin->pude_offset); |
|
dev_dbg(priv->dev, "BIAS(%d): off = 0x%x val = 0x%x\n", |
|
param, pin->pude_offset, val); |
|
break; |
|
|
|
case PIN_CONFIG_DRIVE_STRENGTH: |
|
arg = pinconf_to_config_argument(configs[i]); |
|
dev_dbg(priv->dev, "DRV_STR arg = %d\n", arg); |
|
switch (arg) { |
|
case 2: |
|
case 4: |
|
case 8: |
|
case 16: |
|
case 24: |
|
case 32: |
|
/* |
|
* I/O drive capacity setting: |
|
* 2mA: 0 |
|
* 4mA: 1 |
|
* 8mA: 3 |
|
* 16mA: 7 |
|
* 24mA: 11 |
|
* 32mA: 15 |
|
*/ |
|
set_val = DIV_ROUND_CLOSEST(arg, 2) - 1; |
|
break; |
|
default: |
|
ret = -EINVAL; |
|
goto err; |
|
} |
|
/* update drive setting */ |
|
val = readl(priv->base + pin->dsel_offset); |
|
val &= ~(DSEL_MASK << pin->dsel_shift); |
|
val |= set_val << pin->dsel_shift; |
|
writel(val, priv->base + pin->dsel_offset); |
|
break; |
|
|
|
default: |
|
ret = -EOPNOTSUPP; |
|
goto err; |
|
} |
|
} |
|
err: |
|
spin_unlock_irqrestore(&priv->lock, flags); |
|
return ret; |
|
} |
|
|
|
static int visconti_pin_config_group_set(struct pinctrl_dev *pctldev, |
|
unsigned int selector, |
|
unsigned long *configs, |
|
unsigned int num_configs) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
const unsigned int *pins; |
|
unsigned int num_pins; |
|
int i, ret; |
|
|
|
pins = priv->devdata->groups[selector].pins; |
|
num_pins = priv->devdata->groups[selector].nr_pins; |
|
|
|
dev_dbg(priv->dev, "%s: select = %d, n_pin = %d, n_config = %d\n", |
|
__func__, selector, num_pins, num_configs); |
|
|
|
for (i = 0; i < num_pins; i++) { |
|
ret = visconti_pin_config_set(pctldev, pins[i], |
|
configs, num_configs); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
static const struct pinconf_ops visconti_pinconf_ops = { |
|
.is_generic = true, |
|
.pin_config_set = visconti_pin_config_set, |
|
.pin_config_group_set = visconti_pin_config_group_set, |
|
.pin_config_config_dbg_show = pinconf_generic_dump_config, |
|
}; |
|
|
|
/* pinctrl */ |
|
static int visconti_get_groups_count(struct pinctrl_dev *pctldev) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return priv->devdata->nr_groups; |
|
} |
|
|
|
static const char *visconti_get_group_name(struct pinctrl_dev *pctldev, |
|
unsigned int selector) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return priv->devdata->groups[selector].name; |
|
} |
|
|
|
static int visconti_get_group_pins(struct pinctrl_dev *pctldev, |
|
unsigned int selector, |
|
const unsigned int **pins, |
|
unsigned int *num_pins) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
*pins = priv->devdata->groups[selector].pins; |
|
*num_pins = priv->devdata->groups[selector].nr_pins; |
|
|
|
return 0; |
|
} |
|
|
|
static const struct pinctrl_ops visconti_pinctrl_ops = { |
|
.get_groups_count = visconti_get_groups_count, |
|
.get_group_name = visconti_get_group_name, |
|
.get_group_pins = visconti_get_group_pins, |
|
.dt_node_to_map = pinconf_generic_dt_node_to_map_group, |
|
.dt_free_map = pinctrl_utils_free_map, |
|
}; |
|
|
|
/* pinmux */ |
|
static int visconti_get_functions_count(struct pinctrl_dev *pctldev) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return priv->devdata->nr_functions; |
|
} |
|
|
|
static const char *visconti_get_function_name(struct pinctrl_dev *pctldev, |
|
unsigned int selector) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
return priv->devdata->functions[selector].name; |
|
} |
|
|
|
static int visconti_get_function_groups(struct pinctrl_dev *pctldev, |
|
unsigned int selector, |
|
const char * const **groups, |
|
unsigned * const num_groups) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
|
|
*groups = priv->devdata->functions[selector].groups; |
|
*num_groups = priv->devdata->functions[selector].nr_groups; |
|
|
|
return 0; |
|
} |
|
|
|
static int visconti_set_mux(struct pinctrl_dev *pctldev, |
|
unsigned int function, unsigned int group) |
|
{ |
|
struct visconti_pinctrl *priv = pinctrl_dev_get_drvdata(pctldev); |
|
const struct visconti_pin_function *func = &priv->devdata->functions[function]; |
|
const struct visconti_pin_group *grp = &priv->devdata->groups[group]; |
|
const struct visconti_mux *mux = &grp->mux; |
|
unsigned int val; |
|
unsigned long flags; |
|
|
|
dev_dbg(priv->dev, "%s: function = %d(%s) group = %d(%s)\n", __func__, |
|
function, func->name, group, grp->name); |
|
|
|
spin_lock_irqsave(&priv->lock, flags); |
|
|
|
/* update mux */ |
|
val = readl(priv->base + mux->offset); |
|
val &= ~mux->mask; |
|
val |= mux->val; |
|
writel(val, priv->base + mux->offset); |
|
|
|
spin_unlock_irqrestore(&priv->lock, flags); |
|
|
|
dev_dbg(priv->dev, "[%x]: 0x%x\n", mux->offset, val); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct pinmux_ops visconti_pinmux_ops = { |
|
.get_functions_count = visconti_get_functions_count, |
|
.get_function_name = visconti_get_function_name, |
|
.get_function_groups = visconti_get_function_groups, |
|
.set_mux = visconti_set_mux, |
|
.strict = true, |
|
}; |
|
|
|
int visconti_pinctrl_probe(struct platform_device *pdev, |
|
const struct visconti_pinctrl_devdata *devdata) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct visconti_pinctrl *priv; |
|
struct pinctrl_pin_desc *pins; |
|
int i, ret; |
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
priv->dev = dev; |
|
priv->devdata = devdata; |
|
spin_lock_init(&priv->lock); |
|
|
|
priv->base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(priv->base)) { |
|
dev_err(dev, "unable to map I/O space\n"); |
|
return PTR_ERR(priv->base); |
|
} |
|
|
|
pins = devm_kcalloc(dev, devdata->nr_pins, |
|
sizeof(*pins), GFP_KERNEL); |
|
if (!pins) |
|
return -ENOMEM; |
|
|
|
for (i = 0; i < devdata->nr_pins; i++) |
|
pins[i] = devdata->pins[i].pin; |
|
|
|
priv->pctl_desc.name = dev_name(dev); |
|
priv->pctl_desc.owner = THIS_MODULE; |
|
priv->pctl_desc.pins = pins; |
|
priv->pctl_desc.npins = devdata->nr_pins; |
|
priv->pctl_desc.confops = &visconti_pinconf_ops; |
|
priv->pctl_desc.pctlops = &visconti_pinctrl_ops; |
|
priv->pctl_desc.pmxops = &visconti_pinmux_ops; |
|
|
|
ret = devm_pinctrl_register_and_init(dev, &priv->pctl_desc, |
|
priv, &priv->pctl); |
|
if (ret) { |
|
dev_err(dev, "couldn't register pinctrl: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
if (devdata->unlock) |
|
devdata->unlock(priv->base); |
|
|
|
return pinctrl_enable(priv->pctl); |
|
}
|
|
|