mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-05 01:54:09 +08:00
c341ac65bf
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). To convert the sprd-sc9860 driver, make sprd_pinctrl_remove() return void (instead of zero) and use .remove_new as callback. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Link: https://lore.kernel.org/r/20231009162510.335208-4-u.kleine-koenig@pengutronix.de Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
1139 lines
27 KiB
C
1139 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Spreadtrum pin controller driver
|
|
* Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.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 "../core.h"
|
|
#include "../pinmux.h"
|
|
#include "../pinconf.h"
|
|
#include "../pinctrl-utils.h"
|
|
#include "pinctrl-sprd.h"
|
|
|
|
#define PINCTRL_BIT_MASK(width) (~(~0UL << (width)))
|
|
#define PINCTRL_REG_OFFSET 0x20
|
|
#define PINCTRL_REG_MISC_OFFSET 0x4020
|
|
#define PINCTRL_REG_LEN 0x4
|
|
|
|
#define PIN_FUNC_MASK (BIT(4) | BIT(5))
|
|
#define PIN_FUNC_SEL_1 ~PIN_FUNC_MASK
|
|
#define PIN_FUNC_SEL_2 BIT(4)
|
|
#define PIN_FUNC_SEL_3 BIT(5)
|
|
#define PIN_FUNC_SEL_4 PIN_FUNC_MASK
|
|
|
|
#define AP_SLEEP_MODE BIT(13)
|
|
#define PUBCP_SLEEP_MODE BIT(14)
|
|
#define TGLDSP_SLEEP_MODE BIT(15)
|
|
#define AGDSP_SLEEP_MODE BIT(16)
|
|
#define CM4_SLEEP_MODE BIT(17)
|
|
#define SLEEP_MODE_MASK GENMASK(5, 0)
|
|
#define SLEEP_MODE_SHIFT 13
|
|
|
|
#define SLEEP_INPUT BIT(1)
|
|
#define SLEEP_INPUT_MASK 0x1
|
|
#define SLEEP_INPUT_SHIFT 1
|
|
|
|
#define SLEEP_OUTPUT BIT(0)
|
|
#define SLEEP_OUTPUT_MASK 0x1
|
|
#define SLEEP_OUTPUT_SHIFT 0
|
|
|
|
#define DRIVE_STRENGTH_MASK GENMASK(3, 0)
|
|
#define DRIVE_STRENGTH_SHIFT 19
|
|
|
|
#define SLEEP_PULL_DOWN BIT(2)
|
|
#define SLEEP_PULL_DOWN_MASK 0x1
|
|
#define SLEEP_PULL_DOWN_SHIFT 2
|
|
|
|
#define PULL_DOWN BIT(6)
|
|
#define PULL_DOWN_MASK 0x1
|
|
#define PULL_DOWN_SHIFT 6
|
|
|
|
#define SLEEP_PULL_UP BIT(3)
|
|
#define SLEEP_PULL_UP_MASK 0x1
|
|
#define SLEEP_PULL_UP_SHIFT 3
|
|
|
|
#define PULL_UP_4_7K (BIT(12) | BIT(7))
|
|
#define PULL_UP_20K BIT(7)
|
|
#define PULL_UP_MASK 0x21
|
|
#define PULL_UP_SHIFT 7
|
|
|
|
#define INPUT_SCHMITT BIT(11)
|
|
#define INPUT_SCHMITT_MASK 0x1
|
|
#define INPUT_SCHMITT_SHIFT 11
|
|
|
|
enum pin_sleep_mode {
|
|
AP_SLEEP = BIT(0),
|
|
PUBCP_SLEEP = BIT(1),
|
|
TGLDSP_SLEEP = BIT(2),
|
|
AGDSP_SLEEP = BIT(3),
|
|
CM4_SLEEP = BIT(4),
|
|
};
|
|
|
|
enum pin_func_sel {
|
|
PIN_FUNC_1,
|
|
PIN_FUNC_2,
|
|
PIN_FUNC_3,
|
|
PIN_FUNC_4,
|
|
PIN_FUNC_MAX,
|
|
};
|
|
|
|
/**
|
|
* struct sprd_pin: represent one pin's description
|
|
* @name: pin name
|
|
* @number: pin number
|
|
* @type: pin type, can be GLOBAL_CTRL_PIN/COMMON_PIN/MISC_PIN
|
|
* @reg: pin register address
|
|
* @bit_offset: bit offset in pin register
|
|
* @bit_width: bit width in pin register
|
|
*/
|
|
struct sprd_pin {
|
|
const char *name;
|
|
unsigned int number;
|
|
enum pin_type type;
|
|
unsigned long reg;
|
|
unsigned long bit_offset;
|
|
unsigned long bit_width;
|
|
};
|
|
|
|
/**
|
|
* struct sprd_pin_group: represent one group's description
|
|
* @name: group name
|
|
* @npins: pin numbers of this group
|
|
* @pins: pointer to pins array
|
|
*/
|
|
struct sprd_pin_group {
|
|
const char *name;
|
|
unsigned int npins;
|
|
unsigned int *pins;
|
|
};
|
|
|
|
/**
|
|
* struct sprd_pinctrl_soc_info: represent the SoC's pins description
|
|
* @groups: pointer to groups of pins
|
|
* @ngroups: group numbers of the whole SoC
|
|
* @pins: pointer to pins description
|
|
* @npins: pin numbers of the whole SoC
|
|
* @grp_names: pointer to group names array
|
|
*/
|
|
struct sprd_pinctrl_soc_info {
|
|
struct sprd_pin_group *groups;
|
|
unsigned int ngroups;
|
|
struct sprd_pin *pins;
|
|
unsigned int npins;
|
|
const char **grp_names;
|
|
};
|
|
|
|
/**
|
|
* struct sprd_pinctrl: represent the pin controller device
|
|
* @dev: pointer to the device structure
|
|
* @pctl: pointer to the pinctrl handle
|
|
* @base: base address of the controller
|
|
* @info: pointer to SoC's pins description information
|
|
*/
|
|
struct sprd_pinctrl {
|
|
struct device *dev;
|
|
struct pinctrl_dev *pctl;
|
|
void __iomem *base;
|
|
struct sprd_pinctrl_soc_info *info;
|
|
};
|
|
|
|
#define SPRD_PIN_CONFIG_CONTROL (PIN_CONFIG_END + 1)
|
|
#define SPRD_PIN_CONFIG_SLEEP_MODE (PIN_CONFIG_END + 2)
|
|
|
|
static int sprd_pinctrl_get_id_by_name(struct sprd_pinctrl *sprd_pctl,
|
|
const char *name)
|
|
{
|
|
struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
|
|
int i;
|
|
|
|
for (i = 0; i < info->npins; i++) {
|
|
if (!strcmp(info->pins[i].name, name))
|
|
return info->pins[i].number;
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static struct sprd_pin *
|
|
sprd_pinctrl_get_pin_by_id(struct sprd_pinctrl *sprd_pctl, unsigned int id)
|
|
{
|
|
struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
|
|
struct sprd_pin *pin = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < info->npins; i++) {
|
|
if (info->pins[i].number == id) {
|
|
pin = &info->pins[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pin;
|
|
}
|
|
|
|
static const struct sprd_pin_group *
|
|
sprd_pinctrl_find_group_by_name(struct sprd_pinctrl *sprd_pctl,
|
|
const char *name)
|
|
{
|
|
struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
|
|
const struct sprd_pin_group *grp = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < info->ngroups; i++) {
|
|
if (!strcmp(info->groups[i].name, name)) {
|
|
grp = &info->groups[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return grp;
|
|
}
|
|
|
|
static int sprd_pctrl_group_count(struct pinctrl_dev *pctldev)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
|
|
return info->ngroups;
|
|
}
|
|
|
|
static const char *sprd_pctrl_group_name(struct pinctrl_dev *pctldev,
|
|
unsigned int selector)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
|
|
return info->groups[selector].name;
|
|
}
|
|
|
|
static int sprd_pctrl_group_pins(struct pinctrl_dev *pctldev,
|
|
unsigned int selector,
|
|
const unsigned int **pins,
|
|
unsigned int *npins)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
|
|
if (selector >= info->ngroups)
|
|
return -EINVAL;
|
|
|
|
*pins = info->groups[selector].pins;
|
|
*npins = info->groups[selector].npins;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sprd_dt_node_to_map(struct pinctrl_dev *pctldev,
|
|
struct device_node *np,
|
|
struct pinctrl_map **map,
|
|
unsigned int *num_maps)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
const struct sprd_pin_group *grp;
|
|
unsigned long *configs = NULL;
|
|
unsigned int num_configs = 0;
|
|
unsigned int reserved_maps = 0;
|
|
unsigned int reserve = 0;
|
|
const char *function;
|
|
enum pinctrl_map_type type;
|
|
int ret;
|
|
|
|
grp = sprd_pinctrl_find_group_by_name(pctl, np->name);
|
|
if (!grp) {
|
|
dev_err(pctl->dev, "unable to find group for node %s\n",
|
|
of_node_full_name(np));
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_count_strings(np, "pins");
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret == 1)
|
|
type = PIN_MAP_TYPE_CONFIGS_PIN;
|
|
else
|
|
type = PIN_MAP_TYPE_CONFIGS_GROUP;
|
|
|
|
ret = of_property_read_string(np, "function", &function);
|
|
if (ret < 0) {
|
|
if (ret != -EINVAL)
|
|
dev_err(pctl->dev,
|
|
"%s: could not parse property function\n",
|
|
of_node_full_name(np));
|
|
function = NULL;
|
|
}
|
|
|
|
ret = pinconf_generic_parse_dt_config(np, pctldev, &configs,
|
|
&num_configs);
|
|
if (ret < 0) {
|
|
dev_err(pctl->dev, "%s: could not parse node property\n",
|
|
of_node_full_name(np));
|
|
return ret;
|
|
}
|
|
|
|
*map = NULL;
|
|
*num_maps = 0;
|
|
|
|
if (function != NULL)
|
|
reserve++;
|
|
if (num_configs)
|
|
reserve++;
|
|
|
|
ret = pinctrl_utils_reserve_map(pctldev, map, &reserved_maps,
|
|
num_maps, reserve);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
if (function) {
|
|
ret = pinctrl_utils_add_map_mux(pctldev, map,
|
|
&reserved_maps, num_maps,
|
|
grp->name, function);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
if (num_configs) {
|
|
const char *group_or_pin;
|
|
unsigned int pin_id;
|
|
|
|
if (type == PIN_MAP_TYPE_CONFIGS_PIN) {
|
|
pin_id = grp->pins[0];
|
|
group_or_pin = pin_get_name(pctldev, pin_id);
|
|
} else {
|
|
group_or_pin = grp->name;
|
|
}
|
|
|
|
ret = pinctrl_utils_add_map_configs(pctldev, map,
|
|
&reserved_maps, num_maps,
|
|
group_or_pin, configs,
|
|
num_configs, type);
|
|
}
|
|
|
|
out:
|
|
kfree(configs);
|
|
return ret;
|
|
}
|
|
|
|
static void sprd_pctrl_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 sprd_pctrl_ops = {
|
|
.get_groups_count = sprd_pctrl_group_count,
|
|
.get_group_name = sprd_pctrl_group_name,
|
|
.get_group_pins = sprd_pctrl_group_pins,
|
|
.pin_dbg_show = sprd_pctrl_dbg_show,
|
|
.dt_node_to_map = sprd_dt_node_to_map,
|
|
.dt_free_map = pinctrl_utils_free_map,
|
|
};
|
|
|
|
static int sprd_pmx_get_function_count(struct pinctrl_dev *pctldev)
|
|
{
|
|
return PIN_FUNC_MAX;
|
|
}
|
|
|
|
static const char *sprd_pmx_get_function_name(struct pinctrl_dev *pctldev,
|
|
unsigned int selector)
|
|
{
|
|
switch (selector) {
|
|
case PIN_FUNC_1:
|
|
return "func1";
|
|
case PIN_FUNC_2:
|
|
return "func2";
|
|
case PIN_FUNC_3:
|
|
return "func3";
|
|
case PIN_FUNC_4:
|
|
return "func4";
|
|
default:
|
|
return "null";
|
|
}
|
|
}
|
|
|
|
static int sprd_pmx_get_function_groups(struct pinctrl_dev *pctldev,
|
|
unsigned int selector,
|
|
const char * const **groups,
|
|
unsigned int * const num_groups)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
|
|
*groups = info->grp_names;
|
|
*num_groups = info->ngroups;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sprd_pmx_set_mux(struct pinctrl_dev *pctldev,
|
|
unsigned int func_selector,
|
|
unsigned int group_selector)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
struct sprd_pin_group *grp = &info->groups[group_selector];
|
|
unsigned int i, grp_pins = grp->npins;
|
|
unsigned long reg;
|
|
unsigned int val = 0;
|
|
|
|
if (group_selector >= info->ngroups)
|
|
return -EINVAL;
|
|
|
|
switch (func_selector) {
|
|
case PIN_FUNC_1:
|
|
val &= PIN_FUNC_SEL_1;
|
|
break;
|
|
case PIN_FUNC_2:
|
|
val |= PIN_FUNC_SEL_2;
|
|
break;
|
|
case PIN_FUNC_3:
|
|
val |= PIN_FUNC_SEL_3;
|
|
break;
|
|
case PIN_FUNC_4:
|
|
val |= PIN_FUNC_SEL_4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < grp_pins; i++) {
|
|
unsigned int pin_id = grp->pins[i];
|
|
struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);
|
|
|
|
if (!pin || pin->type != COMMON_PIN)
|
|
continue;
|
|
|
|
reg = readl((void __iomem *)pin->reg);
|
|
reg &= ~PIN_FUNC_MASK;
|
|
reg |= val;
|
|
writel(reg, (void __iomem *)pin->reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pinmux_ops sprd_pmx_ops = {
|
|
.get_functions_count = sprd_pmx_get_function_count,
|
|
.get_function_name = sprd_pmx_get_function_name,
|
|
.get_function_groups = sprd_pmx_get_function_groups,
|
|
.set_mux = sprd_pmx_set_mux,
|
|
};
|
|
|
|
static int sprd_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin_id,
|
|
unsigned long *config)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);
|
|
unsigned int param = pinconf_to_config_param(*config);
|
|
unsigned int reg, arg;
|
|
|
|
if (!pin)
|
|
return -EINVAL;
|
|
|
|
if (pin->type == GLOBAL_CTRL_PIN) {
|
|
reg = (readl((void __iomem *)pin->reg) >>
|
|
pin->bit_offset) & PINCTRL_BIT_MASK(pin->bit_width);
|
|
} else {
|
|
reg = readl((void __iomem *)pin->reg);
|
|
}
|
|
|
|
if (pin->type == GLOBAL_CTRL_PIN &&
|
|
param == SPRD_PIN_CONFIG_CONTROL) {
|
|
arg = reg;
|
|
} else if (pin->type == COMMON_PIN || pin->type == MISC_PIN) {
|
|
switch (param) {
|
|
case SPRD_PIN_CONFIG_SLEEP_MODE:
|
|
arg = (reg >> SLEEP_MODE_SHIFT) & SLEEP_MODE_MASK;
|
|
break;
|
|
case PIN_CONFIG_INPUT_ENABLE:
|
|
arg = (reg >> SLEEP_INPUT_SHIFT) & SLEEP_INPUT_MASK;
|
|
break;
|
|
case PIN_CONFIG_OUTPUT_ENABLE:
|
|
arg = reg & SLEEP_OUTPUT_MASK;
|
|
break;
|
|
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
|
|
if ((reg & SLEEP_OUTPUT) || (reg & SLEEP_INPUT))
|
|
return -EINVAL;
|
|
|
|
arg = 1;
|
|
break;
|
|
case PIN_CONFIG_DRIVE_STRENGTH:
|
|
arg = (reg >> DRIVE_STRENGTH_SHIFT) &
|
|
DRIVE_STRENGTH_MASK;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_DOWN:
|
|
/* combine sleep pull down and pull down config */
|
|
arg = ((reg >> SLEEP_PULL_DOWN_SHIFT) &
|
|
SLEEP_PULL_DOWN_MASK) << 16;
|
|
arg |= (reg >> PULL_DOWN_SHIFT) & PULL_DOWN_MASK;
|
|
break;
|
|
case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
|
|
arg = (reg >> INPUT_SCHMITT_SHIFT) & INPUT_SCHMITT_MASK;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_UP:
|
|
/* combine sleep pull up and pull up config */
|
|
arg = ((reg >> SLEEP_PULL_UP_SHIFT) &
|
|
SLEEP_PULL_UP_MASK) << 16;
|
|
arg |= (reg >> PULL_UP_SHIFT) & PULL_UP_MASK;
|
|
break;
|
|
case PIN_CONFIG_BIAS_DISABLE:
|
|
if ((reg & (SLEEP_PULL_DOWN | SLEEP_PULL_UP)) ||
|
|
(reg & (PULL_DOWN | PULL_UP_4_7K | PULL_UP_20K)))
|
|
return -EINVAL;
|
|
|
|
arg = 1;
|
|
break;
|
|
case PIN_CONFIG_SLEEP_HARDWARE_STATE:
|
|
arg = 0;
|
|
break;
|
|
default:
|
|
return -ENOTSUPP;
|
|
}
|
|
} else {
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
*config = pinconf_to_config_packed(param, arg);
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int sprd_pinconf_drive(unsigned int mA)
|
|
{
|
|
unsigned int val = 0;
|
|
|
|
switch (mA) {
|
|
case 2:
|
|
break;
|
|
case 4:
|
|
val |= BIT(19);
|
|
break;
|
|
case 6:
|
|
val |= BIT(20);
|
|
break;
|
|
case 8:
|
|
val |= BIT(19) | BIT(20);
|
|
break;
|
|
case 10:
|
|
val |= BIT(21);
|
|
break;
|
|
case 12:
|
|
val |= BIT(21) | BIT(19);
|
|
break;
|
|
case 14:
|
|
val |= BIT(21) | BIT(20);
|
|
break;
|
|
case 16:
|
|
val |= BIT(19) | BIT(20) | BIT(21);
|
|
break;
|
|
case 20:
|
|
val |= BIT(22);
|
|
break;
|
|
case 21:
|
|
val |= BIT(22) | BIT(19);
|
|
break;
|
|
case 24:
|
|
val |= BIT(22) | BIT(20);
|
|
break;
|
|
case 25:
|
|
val |= BIT(22) | BIT(20) | BIT(19);
|
|
break;
|
|
case 27:
|
|
val |= BIT(22) | BIT(21);
|
|
break;
|
|
case 29:
|
|
val |= BIT(22) | BIT(21) | BIT(19);
|
|
break;
|
|
case 31:
|
|
val |= BIT(22) | BIT(21) | BIT(20);
|
|
break;
|
|
case 33:
|
|
val |= BIT(22) | BIT(21) | BIT(20) | BIT(19);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static bool sprd_pinctrl_check_sleep_config(unsigned long *configs,
|
|
unsigned int num_configs)
|
|
{
|
|
unsigned int param;
|
|
int i;
|
|
|
|
for (i = 0; i < num_configs; i++) {
|
|
param = pinconf_to_config_param(configs[i]);
|
|
if (param == PIN_CONFIG_SLEEP_HARDWARE_STATE)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int sprd_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin_id,
|
|
unsigned long *configs, unsigned int num_configs)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);
|
|
bool is_sleep_config;
|
|
unsigned long reg;
|
|
int i;
|
|
|
|
if (!pin)
|
|
return -EINVAL;
|
|
|
|
is_sleep_config = sprd_pinctrl_check_sleep_config(configs, num_configs);
|
|
|
|
for (i = 0; i < num_configs; i++) {
|
|
unsigned int param, arg, shift, mask, val;
|
|
|
|
param = pinconf_to_config_param(configs[i]);
|
|
arg = pinconf_to_config_argument(configs[i]);
|
|
|
|
val = 0;
|
|
shift = 0;
|
|
mask = 0;
|
|
if (pin->type == GLOBAL_CTRL_PIN &&
|
|
param == SPRD_PIN_CONFIG_CONTROL) {
|
|
val = arg;
|
|
} else if (pin->type == COMMON_PIN || pin->type == MISC_PIN) {
|
|
switch (param) {
|
|
case SPRD_PIN_CONFIG_SLEEP_MODE:
|
|
if (arg & AP_SLEEP)
|
|
val |= AP_SLEEP_MODE;
|
|
if (arg & PUBCP_SLEEP)
|
|
val |= PUBCP_SLEEP_MODE;
|
|
if (arg & TGLDSP_SLEEP)
|
|
val |= TGLDSP_SLEEP_MODE;
|
|
if (arg & AGDSP_SLEEP)
|
|
val |= AGDSP_SLEEP_MODE;
|
|
if (arg & CM4_SLEEP)
|
|
val |= CM4_SLEEP_MODE;
|
|
|
|
mask = SLEEP_MODE_MASK;
|
|
shift = SLEEP_MODE_SHIFT;
|
|
break;
|
|
case PIN_CONFIG_INPUT_ENABLE:
|
|
if (is_sleep_config == true) {
|
|
if (arg > 0)
|
|
val |= SLEEP_INPUT;
|
|
else
|
|
val &= ~SLEEP_INPUT;
|
|
|
|
mask = SLEEP_INPUT_MASK;
|
|
shift = SLEEP_INPUT_SHIFT;
|
|
}
|
|
break;
|
|
case PIN_CONFIG_OUTPUT_ENABLE:
|
|
if (is_sleep_config == true) {
|
|
if (arg > 0)
|
|
val |= SLEEP_OUTPUT;
|
|
else
|
|
val &= ~SLEEP_OUTPUT;
|
|
|
|
mask = SLEEP_OUTPUT_MASK;
|
|
shift = SLEEP_OUTPUT_SHIFT;
|
|
}
|
|
break;
|
|
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
|
|
if (is_sleep_config == true) {
|
|
val = shift = 0;
|
|
mask = SLEEP_OUTPUT | SLEEP_INPUT;
|
|
}
|
|
break;
|
|
case PIN_CONFIG_DRIVE_STRENGTH:
|
|
if (arg < 2 || arg > 60)
|
|
return -EINVAL;
|
|
|
|
val = sprd_pinconf_drive(arg);
|
|
mask = DRIVE_STRENGTH_MASK;
|
|
shift = DRIVE_STRENGTH_SHIFT;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_DOWN:
|
|
if (is_sleep_config == true) {
|
|
val |= SLEEP_PULL_DOWN;
|
|
mask = SLEEP_PULL_DOWN_MASK;
|
|
shift = SLEEP_PULL_DOWN_SHIFT;
|
|
} else {
|
|
val |= PULL_DOWN;
|
|
mask = PULL_DOWN_MASK;
|
|
shift = PULL_DOWN_SHIFT;
|
|
}
|
|
break;
|
|
case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
|
|
if (arg > 0)
|
|
val |= INPUT_SCHMITT;
|
|
else
|
|
val &= ~INPUT_SCHMITT;
|
|
|
|
mask = INPUT_SCHMITT_MASK;
|
|
shift = INPUT_SCHMITT_SHIFT;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_UP:
|
|
if (is_sleep_config) {
|
|
val |= SLEEP_PULL_UP;
|
|
mask = SLEEP_PULL_UP_MASK;
|
|
shift = SLEEP_PULL_UP_SHIFT;
|
|
} else {
|
|
if (arg == 20000)
|
|
val |= PULL_UP_20K;
|
|
else if (arg == 4700)
|
|
val |= PULL_UP_4_7K;
|
|
|
|
mask = PULL_UP_MASK;
|
|
shift = PULL_UP_SHIFT;
|
|
}
|
|
break;
|
|
case PIN_CONFIG_BIAS_DISABLE:
|
|
if (is_sleep_config == true) {
|
|
val = shift = 0;
|
|
mask = SLEEP_PULL_DOWN | SLEEP_PULL_UP;
|
|
} else {
|
|
val = shift = 0;
|
|
mask = PULL_DOWN | PULL_UP_20K |
|
|
PULL_UP_4_7K;
|
|
}
|
|
break;
|
|
case PIN_CONFIG_SLEEP_HARDWARE_STATE:
|
|
continue;
|
|
default:
|
|
return -ENOTSUPP;
|
|
}
|
|
} else {
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
if (pin->type == GLOBAL_CTRL_PIN) {
|
|
reg = readl((void __iomem *)pin->reg);
|
|
reg &= ~(PINCTRL_BIT_MASK(pin->bit_width)
|
|
<< pin->bit_offset);
|
|
reg |= (val & PINCTRL_BIT_MASK(pin->bit_width))
|
|
<< pin->bit_offset;
|
|
writel(reg, (void __iomem *)pin->reg);
|
|
} else {
|
|
reg = readl((void __iomem *)pin->reg);
|
|
reg &= ~(mask << shift);
|
|
reg |= val;
|
|
writel(reg, (void __iomem *)pin->reg);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sprd_pinconf_group_get(struct pinctrl_dev *pctldev,
|
|
unsigned int selector, unsigned long *config)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
struct sprd_pin_group *grp;
|
|
unsigned int pin_id;
|
|
|
|
if (selector >= info->ngroups)
|
|
return -EINVAL;
|
|
|
|
grp = &info->groups[selector];
|
|
pin_id = grp->pins[0];
|
|
|
|
return sprd_pinconf_get(pctldev, pin_id, config);
|
|
}
|
|
|
|
static int sprd_pinconf_group_set(struct pinctrl_dev *pctldev,
|
|
unsigned int selector,
|
|
unsigned long *configs,
|
|
unsigned int num_configs)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
struct sprd_pin_group *grp;
|
|
int ret, i;
|
|
|
|
if (selector >= info->ngroups)
|
|
return -EINVAL;
|
|
|
|
grp = &info->groups[selector];
|
|
|
|
for (i = 0; i < grp->npins; i++) {
|
|
unsigned int pin_id = grp->pins[i];
|
|
|
|
ret = sprd_pinconf_set(pctldev, pin_id, configs, num_configs);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sprd_pinconf_get_config(struct pinctrl_dev *pctldev,
|
|
unsigned int pin_id,
|
|
unsigned long *config)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pin *pin = sprd_pinctrl_get_pin_by_id(pctl, pin_id);
|
|
|
|
if (!pin)
|
|
return -EINVAL;
|
|
|
|
if (pin->type == GLOBAL_CTRL_PIN) {
|
|
*config = (readl((void __iomem *)pin->reg) >>
|
|
pin->bit_offset) & PINCTRL_BIT_MASK(pin->bit_width);
|
|
} else {
|
|
*config = readl((void __iomem *)pin->reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sprd_pinconf_dbg_show(struct pinctrl_dev *pctldev,
|
|
struct seq_file *s, unsigned int pin_id)
|
|
{
|
|
unsigned long config;
|
|
int ret;
|
|
|
|
ret = sprd_pinconf_get_config(pctldev, pin_id, &config);
|
|
if (ret)
|
|
return;
|
|
|
|
seq_printf(s, "0x%lx", config);
|
|
}
|
|
|
|
static void sprd_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
|
|
struct seq_file *s,
|
|
unsigned int selector)
|
|
{
|
|
struct sprd_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev);
|
|
struct sprd_pinctrl_soc_info *info = pctl->info;
|
|
struct sprd_pin_group *grp;
|
|
unsigned long config;
|
|
const char *name;
|
|
int i, ret;
|
|
|
|
if (selector >= info->ngroups)
|
|
return;
|
|
|
|
grp = &info->groups[selector];
|
|
|
|
seq_putc(s, '\n');
|
|
for (i = 0; i < grp->npins; i++, config++) {
|
|
unsigned int pin_id = grp->pins[i];
|
|
|
|
name = pin_get_name(pctldev, pin_id);
|
|
ret = sprd_pinconf_get_config(pctldev, pin_id, &config);
|
|
if (ret)
|
|
return;
|
|
|
|
seq_printf(s, "%s: 0x%lx ", name, config);
|
|
}
|
|
}
|
|
|
|
static const struct pinconf_ops sprd_pinconf_ops = {
|
|
.is_generic = true,
|
|
.pin_config_get = sprd_pinconf_get,
|
|
.pin_config_set = sprd_pinconf_set,
|
|
.pin_config_group_get = sprd_pinconf_group_get,
|
|
.pin_config_group_set = sprd_pinconf_group_set,
|
|
.pin_config_dbg_show = sprd_pinconf_dbg_show,
|
|
.pin_config_group_dbg_show = sprd_pinconf_group_dbg_show,
|
|
};
|
|
|
|
static const struct pinconf_generic_params sprd_dt_params[] = {
|
|
{"sprd,control", SPRD_PIN_CONFIG_CONTROL, 0},
|
|
{"sprd,sleep-mode", SPRD_PIN_CONFIG_SLEEP_MODE, 0},
|
|
};
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static const struct pin_config_item sprd_conf_items[] = {
|
|
PCONFDUMP(SPRD_PIN_CONFIG_CONTROL, "global control", NULL, true),
|
|
PCONFDUMP(SPRD_PIN_CONFIG_SLEEP_MODE, "sleep mode", NULL, true),
|
|
};
|
|
#endif
|
|
|
|
static struct pinctrl_desc sprd_pinctrl_desc = {
|
|
.pctlops = &sprd_pctrl_ops,
|
|
.pmxops = &sprd_pmx_ops,
|
|
.confops = &sprd_pinconf_ops,
|
|
.num_custom_params = ARRAY_SIZE(sprd_dt_params),
|
|
.custom_params = sprd_dt_params,
|
|
#ifdef CONFIG_DEBUG_FS
|
|
.custom_conf_items = sprd_conf_items,
|
|
#endif
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static int sprd_pinctrl_parse_groups(struct device_node *np,
|
|
struct sprd_pinctrl *sprd_pctl,
|
|
struct sprd_pin_group *grp)
|
|
{
|
|
struct property *prop;
|
|
const char *pin_name;
|
|
int ret, i = 0;
|
|
|
|
ret = of_property_count_strings(np, "pins");
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
grp->name = np->name;
|
|
grp->npins = ret;
|
|
grp->pins = devm_kcalloc(sprd_pctl->dev,
|
|
grp->npins, sizeof(unsigned int),
|
|
GFP_KERNEL);
|
|
if (!grp->pins)
|
|
return -ENOMEM;
|
|
|
|
of_property_for_each_string(np, "pins", prop, pin_name) {
|
|
ret = sprd_pinctrl_get_id_by_name(sprd_pctl, pin_name);
|
|
if (ret >= 0)
|
|
grp->pins[i++] = ret;
|
|
}
|
|
|
|
for (i = 0; i < grp->npins; i++) {
|
|
dev_dbg(sprd_pctl->dev,
|
|
"Group[%s] contains [%d] pins: id = %d\n",
|
|
grp->name, grp->npins, grp->pins[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int sprd_pinctrl_get_groups(struct device_node *np)
|
|
{
|
|
struct device_node *child;
|
|
unsigned int group_cnt, cnt;
|
|
|
|
group_cnt = of_get_child_count(np);
|
|
|
|
for_each_child_of_node(np, child) {
|
|
cnt = of_get_child_count(child);
|
|
if (cnt > 0)
|
|
group_cnt += cnt;
|
|
}
|
|
|
|
return group_cnt;
|
|
}
|
|
|
|
static int sprd_pinctrl_parse_dt(struct sprd_pinctrl *sprd_pctl)
|
|
{
|
|
struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
|
|
struct device_node *np = sprd_pctl->dev->of_node;
|
|
struct device_node *child, *sub_child;
|
|
struct sprd_pin_group *grp;
|
|
const char **temp;
|
|
int ret;
|
|
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
info->ngroups = sprd_pinctrl_get_groups(np);
|
|
if (!info->ngroups)
|
|
return 0;
|
|
|
|
info->groups = devm_kcalloc(sprd_pctl->dev,
|
|
info->ngroups,
|
|
sizeof(struct sprd_pin_group),
|
|
GFP_KERNEL);
|
|
if (!info->groups)
|
|
return -ENOMEM;
|
|
|
|
info->grp_names = devm_kcalloc(sprd_pctl->dev,
|
|
info->ngroups, sizeof(char *),
|
|
GFP_KERNEL);
|
|
if (!info->grp_names)
|
|
return -ENOMEM;
|
|
|
|
temp = info->grp_names;
|
|
grp = info->groups;
|
|
|
|
for_each_child_of_node(np, child) {
|
|
ret = sprd_pinctrl_parse_groups(child, sprd_pctl, grp);
|
|
if (ret) {
|
|
of_node_put(child);
|
|
return ret;
|
|
}
|
|
|
|
*temp++ = grp->name;
|
|
grp++;
|
|
|
|
if (of_get_child_count(child) > 0) {
|
|
for_each_child_of_node(child, sub_child) {
|
|
ret = sprd_pinctrl_parse_groups(sub_child,
|
|
sprd_pctl, grp);
|
|
if (ret) {
|
|
of_node_put(sub_child);
|
|
of_node_put(child);
|
|
return ret;
|
|
}
|
|
|
|
*temp++ = grp->name;
|
|
grp++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sprd_pinctrl_add_pins(struct sprd_pinctrl *sprd_pctl,
|
|
struct sprd_pins_info *sprd_soc_pin_info,
|
|
int pins_cnt)
|
|
{
|
|
struct sprd_pinctrl_soc_info *info = sprd_pctl->info;
|
|
unsigned int ctrl_pin = 0, com_pin = 0;
|
|
struct sprd_pin *pin;
|
|
int i;
|
|
|
|
info->npins = pins_cnt;
|
|
info->pins = devm_kcalloc(sprd_pctl->dev,
|
|
info->npins, sizeof(struct sprd_pin),
|
|
GFP_KERNEL);
|
|
if (!info->pins)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0, pin = info->pins; i < info->npins; i++, pin++) {
|
|
unsigned int reg;
|
|
|
|
pin->name = sprd_soc_pin_info[i].name;
|
|
pin->type = sprd_soc_pin_info[i].type;
|
|
pin->number = sprd_soc_pin_info[i].num;
|
|
reg = sprd_soc_pin_info[i].reg;
|
|
if (pin->type == GLOBAL_CTRL_PIN) {
|
|
pin->reg = (unsigned long)sprd_pctl->base +
|
|
PINCTRL_REG_LEN * reg;
|
|
pin->bit_offset = sprd_soc_pin_info[i].bit_offset;
|
|
pin->bit_width = sprd_soc_pin_info[i].bit_width;
|
|
ctrl_pin++;
|
|
} else if (pin->type == COMMON_PIN) {
|
|
pin->reg = (unsigned long)sprd_pctl->base +
|
|
PINCTRL_REG_OFFSET + PINCTRL_REG_LEN *
|
|
(i - ctrl_pin);
|
|
com_pin++;
|
|
} else if (pin->type == MISC_PIN) {
|
|
pin->reg = (unsigned long)sprd_pctl->base +
|
|
PINCTRL_REG_MISC_OFFSET + PINCTRL_REG_LEN *
|
|
(i - ctrl_pin - com_pin);
|
|
}
|
|
}
|
|
|
|
for (i = 0, pin = info->pins; i < info->npins; pin++, i++) {
|
|
dev_dbg(sprd_pctl->dev, "pin name[%s-%d], type = %d, "
|
|
"bit offset = %ld, bit width = %ld, reg = 0x%lx\n",
|
|
pin->name, pin->number, pin->type,
|
|
pin->bit_offset, pin->bit_width, pin->reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sprd_pinctrl_core_probe(struct platform_device *pdev,
|
|
struct sprd_pins_info *sprd_soc_pin_info,
|
|
int pins_cnt)
|
|
{
|
|
struct sprd_pinctrl *sprd_pctl;
|
|
struct sprd_pinctrl_soc_info *pinctrl_info;
|
|
struct pinctrl_pin_desc *pin_desc;
|
|
int ret, i;
|
|
|
|
sprd_pctl = devm_kzalloc(&pdev->dev, sizeof(struct sprd_pinctrl),
|
|
GFP_KERNEL);
|
|
if (!sprd_pctl)
|
|
return -ENOMEM;
|
|
|
|
sprd_pctl->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(sprd_pctl->base))
|
|
return PTR_ERR(sprd_pctl->base);
|
|
|
|
pinctrl_info = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct sprd_pinctrl_soc_info),
|
|
GFP_KERNEL);
|
|
if (!pinctrl_info)
|
|
return -ENOMEM;
|
|
|
|
sprd_pctl->info = pinctrl_info;
|
|
sprd_pctl->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, sprd_pctl);
|
|
|
|
ret = sprd_pinctrl_add_pins(sprd_pctl, sprd_soc_pin_info, pins_cnt);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "fail to add pins information\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = sprd_pinctrl_parse_dt(sprd_pctl);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "fail to parse dt properties\n");
|
|
return ret;
|
|
}
|
|
|
|
pin_desc = devm_kcalloc(&pdev->dev,
|
|
pinctrl_info->npins,
|
|
sizeof(struct pinctrl_pin_desc),
|
|
GFP_KERNEL);
|
|
if (!pin_desc)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < pinctrl_info->npins; i++) {
|
|
pin_desc[i].number = pinctrl_info->pins[i].number;
|
|
pin_desc[i].name = pinctrl_info->pins[i].name;
|
|
pin_desc[i].drv_data = pinctrl_info;
|
|
}
|
|
|
|
sprd_pinctrl_desc.pins = pin_desc;
|
|
sprd_pinctrl_desc.name = dev_name(&pdev->dev);
|
|
sprd_pinctrl_desc.npins = pinctrl_info->npins;
|
|
|
|
sprd_pctl->pctl = pinctrl_register(&sprd_pinctrl_desc,
|
|
&pdev->dev, (void *)sprd_pctl);
|
|
if (IS_ERR(sprd_pctl->pctl)) {
|
|
dev_err(&pdev->dev, "could not register pinctrl driver\n");
|
|
return PTR_ERR(sprd_pctl->pctl);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(sprd_pinctrl_core_probe);
|
|
|
|
void sprd_pinctrl_remove(struct platform_device *pdev)
|
|
{
|
|
struct sprd_pinctrl *sprd_pctl = platform_get_drvdata(pdev);
|
|
|
|
pinctrl_unregister(sprd_pctl->pctl);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sprd_pinctrl_remove);
|
|
|
|
void sprd_pinctrl_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct pinctrl *pinctl;
|
|
struct pinctrl_state *state;
|
|
|
|
pinctl = devm_pinctrl_get(&pdev->dev);
|
|
if (IS_ERR(pinctl))
|
|
return;
|
|
state = pinctrl_lookup_state(pinctl, "shutdown");
|
|
if (IS_ERR(state))
|
|
return;
|
|
pinctrl_select_state(pinctl, state);
|
|
}
|
|
EXPORT_SYMBOL_GPL(sprd_pinctrl_shutdown);
|
|
|
|
MODULE_DESCRIPTION("SPREADTRUM Pin Controller Driver");
|
|
MODULE_AUTHOR("Baolin Wang <baolin.wang@spreadtrum.com>");
|
|
MODULE_LICENSE("GPL v2");
|