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.
1137 lines
26 KiB
1137 lines
26 KiB
// 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/of_device.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/pinctrl/machine.h> |
|
#include <linux/pinctrl/pinconf.h> |
|
#include <linux/pinctrl/pinconf-generic.h> |
|
#include <linux/pinctrl/pinctrl.h> |
|
#include <linux/pinctrl/pinmux.h> |
|
#include <linux/slab.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); |
|
|
|
int sprd_pinctrl_remove(struct platform_device *pdev) |
|
{ |
|
struct sprd_pinctrl *sprd_pctl = platform_get_drvdata(pdev); |
|
|
|
pinctrl_unregister(sprd_pctl->pctl); |
|
return 0; |
|
} |
|
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 <[email protected]>"); |
|
MODULE_LICENSE("GPL v2");
|
|
|