blob: e3030109a4e595882e65977d0dfb67d56a569a69 [file] [log] [blame]
/*
* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/platform_device.h>
#include <linux/qdsp6v2/audio_notifier.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "../core.h"
#include "../pinctrl-utils.h"
#define LPI_ADDRESS_SIZE 0xC000
#define LPI_GPIO_REG_VAL_CTL 0x00
#define LPI_GPIO_REG_DIR_CTL 0x04
#define LPI_GPIO_REG_PULL_SHIFT 0x0
#define LPI_GPIO_REG_PULL_MASK 0x3
#define LPI_GPIO_REG_FUNCTION_SHIFT 0x2
#define LPI_GPIO_REG_FUNCTION_MASK 0x3C
#define LPI_GPIO_REG_OUT_STRENGTH_SHIFT 0x6
#define LPI_GPIO_REG_OUT_STRENGTH_MASK 0x1C0
#define LPI_GPIO_REG_OE_SHIFT 0x9
#define LPI_GPIO_REG_OE_MASK 0x200
#define LPI_GPIO_REG_DIR_SHIFT 0x1
#define LPI_GPIO_REG_DIR_MASK 0x2
#define LPI_GPIO_BIAS_DISABLE 0x0
#define LPI_GPIO_PULL_DOWN 0x1
#define LPI_GPIO_KEEPER 0x2
#define LPI_GPIO_PULL_UP 0x3
#define LPI_GPIO_FUNC_GPIO "gpio"
#define LPI_GPIO_FUNC_FUNC1 "func1"
#define LPI_GPIO_FUNC_FUNC2 "func2"
#define LPI_GPIO_FUNC_FUNC3 "func3"
#define LPI_GPIO_FUNC_FUNC4 "func4"
#define LPI_GPIO_FUNC_FUNC5 "func5"
static bool lpi_dev_up;
/* The index of each function in lpi_gpio_functions[] array */
enum lpi_gpio_func_index {
LPI_GPIO_FUNC_INDEX_GPIO = 0x00,
LPI_GPIO_FUNC_INDEX_FUNC1 = 0x01,
LPI_GPIO_FUNC_INDEX_FUNC2 = 0x02,
LPI_GPIO_FUNC_INDEX_FUNC3 = 0x03,
LPI_GPIO_FUNC_INDEX_FUNC4 = 0x04,
LPI_GPIO_FUNC_INDEX_FUNC5 = 0x05,
};
/**
* struct lpi_gpio_pad - keep current GPIO settings
* @offset: Nth GPIO in supported GPIOs.
* @output_enabled: Set to true if GPIO output logic is enabled.
* @value: value of a pin
* @base: Address base of LPI GPIO PAD.
* @pullup: Constant current which flow through GPIO output buffer.
* @strength: No, Low, Medium, High
* @function: See lpi_gpio_functions[]
*/
struct lpi_gpio_pad {
u16 offset;
bool output_enabled;
bool value;
char __iomem *base;
unsigned int pullup;
unsigned int strength;
unsigned int function;
};
struct lpi_gpio_state {
struct device *dev;
struct pinctrl_dev *ctrl;
struct gpio_chip chip;
char __iomem *base;
};
static const char *const lpi_gpio_groups[] = {
"gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7",
"gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14",
"gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21",
"gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28",
"gpio29", "gpio30", "gpio31",
};
static const u32 lpi_offset[] = {
0x00000000,
0x00001000,
0x00002000,
0x00003000,
0x00003010,
0x00004000,
0x00004010,
0x00005000,
0x00005010,
0x00005020,
0x00005030,
0x00005040,
0x00005050,
0x00006000,
0x00006010,
0x00007000,
0x00007010,
0x00008000,
0x00008010,
0x00008020,
0x00008030,
0x00008040,
0x00008050,
0x00008060,
0x00008070,
0x00009000,
0x00009010,
0x0000A000,
0x0000A010,
0x0000B000,
0x0000B010,
};
static const char *const lpi_gpio_functions[] = {
[LPI_GPIO_FUNC_INDEX_GPIO] = LPI_GPIO_FUNC_GPIO,
[LPI_GPIO_FUNC_INDEX_FUNC1] = LPI_GPIO_FUNC_FUNC1,
[LPI_GPIO_FUNC_INDEX_FUNC2] = LPI_GPIO_FUNC_FUNC2,
[LPI_GPIO_FUNC_INDEX_FUNC3] = LPI_GPIO_FUNC_FUNC3,
[LPI_GPIO_FUNC_INDEX_FUNC4] = LPI_GPIO_FUNC_FUNC4,
[LPI_GPIO_FUNC_INDEX_FUNC5] = LPI_GPIO_FUNC_FUNC5,
};
static int lpi_gpio_read(struct lpi_gpio_pad *pad, unsigned int addr)
{
int ret;
if (!lpi_dev_up) {
pr_err_ratelimited("%s: ADSP is down due to SSR, return\n",
__func__);
return 0;
}
ret = ioread32(pad->base + pad->offset + addr);
if (ret < 0)
pr_err("%s: read 0x%x failed\n", __func__, addr);
return ret;
}
static int lpi_gpio_write(struct lpi_gpio_pad *pad, unsigned int addr,
unsigned int val)
{
if (!lpi_dev_up) {
pr_err_ratelimited("%s: ADSP is down due to SSR, return\n",
__func__);
return 0;
}
iowrite32(val, pad->base + pad->offset + addr);
return 0;
}
static int lpi_gpio_get_groups_count(struct pinctrl_dev *pctldev)
{
/* Every PIN is a group */
return pctldev->desc->npins;
}
static const char *lpi_gpio_get_group_name(struct pinctrl_dev *pctldev,
unsigned int pin)
{
return pctldev->desc->pins[pin].name;
}
static int lpi_gpio_get_group_pins(struct pinctrl_dev *pctldev,
unsigned int pin,
const unsigned int **pins,
unsigned int *num_pins)
{
*pins = &pctldev->desc->pins[pin].number;
*num_pins = 1;
return 0;
}
static const struct pinctrl_ops lpi_gpio_pinctrl_ops = {
.get_groups_count = lpi_gpio_get_groups_count,
.get_group_name = lpi_gpio_get_group_name,
.get_group_pins = lpi_gpio_get_group_pins,
.dt_node_to_map = pinconf_generic_dt_node_to_map_group,
.dt_free_map = pinctrl_utils_free_map,
};
static int lpi_gpio_get_functions_count(struct pinctrl_dev *pctldev)
{
return ARRAY_SIZE(lpi_gpio_functions);
}
static const char *lpi_gpio_get_function_name(struct pinctrl_dev *pctldev,
unsigned int function)
{
return lpi_gpio_functions[function];
}
static int lpi_gpio_get_function_groups(struct pinctrl_dev *pctldev,
unsigned int function,
const char *const **groups,
unsigned *const num_qgroups)
{
*groups = lpi_gpio_groups;
*num_qgroups = pctldev->desc->npins;
return 0;
}
static int lpi_gpio_set_mux(struct pinctrl_dev *pctldev, unsigned int function,
unsigned int pin)
{
struct lpi_gpio_pad *pad;
unsigned int val;
pad = pctldev->desc->pins[pin].drv_data;
pad->function = function;
val = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
val &= ~(LPI_GPIO_REG_FUNCTION_MASK);
val |= pad->function << LPI_GPIO_REG_FUNCTION_SHIFT;
lpi_gpio_write(pad, LPI_GPIO_REG_VAL_CTL, val);
return 0;
}
static const struct pinmux_ops lpi_gpio_pinmux_ops = {
.get_functions_count = lpi_gpio_get_functions_count,
.get_function_name = lpi_gpio_get_function_name,
.get_function_groups = lpi_gpio_get_function_groups,
.set_mux = lpi_gpio_set_mux,
};
static int lpi_config_get(struct pinctrl_dev *pctldev,
unsigned int pin, unsigned long *config)
{
unsigned int param = pinconf_to_config_param(*config);
struct lpi_gpio_pad *pad;
unsigned int arg;
pad = pctldev->desc->pins[pin].drv_data;
switch (param) {
case PIN_CONFIG_BIAS_DISABLE:
arg = pad->pullup = LPI_GPIO_BIAS_DISABLE;
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
arg = pad->pullup == LPI_GPIO_PULL_DOWN;
break;
case PIN_CONFIG_BIAS_BUS_HOLD:
arg = pad->pullup = LPI_GPIO_KEEPER;
break;
case PIN_CONFIG_BIAS_PULL_UP:
arg = pad->pullup == LPI_GPIO_PULL_UP;
break;
case PIN_CONFIG_INPUT_ENABLE:
case PIN_CONFIG_OUTPUT:
arg = pad->output_enabled;
break;
default:
return -EINVAL;
}
*config = pinconf_to_config_packed(param, arg);
return 0;
}
static unsigned int lpi_drive_to_regval(u32 arg)
{
return (arg/2 - 1);
}
static int lpi_config_set(struct pinctrl_dev *pctldev, unsigned int pin,
unsigned long *configs, unsigned int nconfs)
{
struct lpi_gpio_pad *pad;
unsigned int param, arg;
int i, ret = 0, val;
pad = pctldev->desc->pins[pin].drv_data;
for (i = 0; i < nconfs; i++) {
param = pinconf_to_config_param(configs[i]);
arg = pinconf_to_config_argument(configs[i]);
dev_dbg(pctldev->dev, "%s: param: %d arg: %d pin: %d\n",
__func__, param, arg, pin);
switch (param) {
case PIN_CONFIG_BIAS_DISABLE:
pad->pullup = LPI_GPIO_BIAS_DISABLE;
break;
case PIN_CONFIG_BIAS_PULL_DOWN:
pad->pullup = LPI_GPIO_PULL_DOWN;
break;
case PIN_CONFIG_BIAS_BUS_HOLD:
pad->pullup = LPI_GPIO_KEEPER;
break;
case PIN_CONFIG_BIAS_PULL_UP:
pad->pullup = LPI_GPIO_PULL_UP;
break;
case PIN_CONFIG_INPUT_ENABLE:
pad->output_enabled = false;
break;
case PIN_CONFIG_OUTPUT:
pad->output_enabled = true;
pad->value = arg;
break;
case PIN_CONFIG_DRIVE_STRENGTH:
pad->strength = arg;
break;
default:
ret = -EINVAL;
goto done;
}
}
val = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
val &= ~(LPI_GPIO_REG_PULL_MASK | LPI_GPIO_REG_OUT_STRENGTH_MASK |
LPI_GPIO_REG_OE_MASK);
val |= pad->pullup << LPI_GPIO_REG_PULL_SHIFT;
val |= lpi_drive_to_regval(pad->strength) <<
LPI_GPIO_REG_OUT_STRENGTH_SHIFT;
if (pad->output_enabled)
val |= pad->value << LPI_GPIO_REG_OE_SHIFT;
lpi_gpio_write(pad, LPI_GPIO_REG_VAL_CTL, val);
lpi_gpio_write(pad, LPI_GPIO_REG_DIR_CTL,
pad->output_enabled << LPI_GPIO_REG_DIR_SHIFT);
done:
return ret;
}
static const struct pinconf_ops lpi_gpio_pinconf_ops = {
.is_generic = true,
.pin_config_group_get = lpi_config_get,
.pin_config_group_set = lpi_config_set,
};
static int lpi_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
{
struct lpi_gpio_state *state = gpiochip_get_data(chip);
unsigned long config;
config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
return lpi_config_set(state->ctrl, pin, &config, 1);
}
static int lpi_gpio_direction_output(struct gpio_chip *chip,
unsigned int pin, int val)
{
struct lpi_gpio_state *state = gpiochip_get_data(chip);
unsigned long config;
config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val);
return lpi_config_set(state->ctrl, pin, &config, 1);
}
static int lpi_gpio_get(struct gpio_chip *chip, unsigned int pin)
{
struct lpi_gpio_state *state = gpiochip_get_data(chip);
struct lpi_gpio_pad *pad;
int value;
pad = state->ctrl->desc->pins[pin].drv_data;
value = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
return value;
}
static void lpi_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
{
struct lpi_gpio_state *state = gpiochip_get_data(chip);
unsigned long config;
config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value);
lpi_config_set(state->ctrl, pin, &config, 1);
}
static int lpi_notifier_service_cb(struct notifier_block *this,
unsigned long opcode, void *ptr)
{
pr_debug("%s: Service opcode 0x%lx\n", __func__, opcode);
switch (opcode) {
case AUDIO_NOTIFIER_SERVICE_DOWN:
lpi_dev_up = false;
break;
case AUDIO_NOTIFIER_SERVICE_UP:
lpi_dev_up = true;
break;
default:
break;
}
return NOTIFY_OK;
}
static struct notifier_block service_nb = {
.notifier_call = lpi_notifier_service_cb,
.priority = -INT_MAX,
};
#ifdef CONFIG_DEBUG_FS
#include <linux/seq_file.h>
static unsigned int lpi_regval_to_drive(u32 val)
{
return (val + 1) * 2;
}
static void lpi_gpio_dbg_show_one(struct seq_file *s,
struct pinctrl_dev *pctldev,
struct gpio_chip *chip,
unsigned int offset,
unsigned int gpio)
{
struct pinctrl_pin_desc pindesc;
struct lpi_gpio_pad *pad;
unsigned int func;
int is_out;
int drive;
int pull;
u32 ctl_reg;
static const char * const pulls[] = {
"no pull",
"pull down",
"keeper",
"pull up"
};
pindesc = pctldev->desc->pins[offset];
pad = pctldev->desc->pins[offset].drv_data;
ctl_reg = lpi_gpio_read(pad, LPI_GPIO_REG_DIR_CTL);
is_out = (ctl_reg & LPI_GPIO_REG_DIR_MASK) >> LPI_GPIO_REG_DIR_SHIFT;
ctl_reg = lpi_gpio_read(pad, LPI_GPIO_REG_VAL_CTL);
func = (ctl_reg & LPI_GPIO_REG_FUNCTION_MASK) >>
LPI_GPIO_REG_FUNCTION_SHIFT;
drive = (ctl_reg & LPI_GPIO_REG_OUT_STRENGTH_MASK) >>
LPI_GPIO_REG_OUT_STRENGTH_SHIFT;
pull = (ctl_reg & LPI_GPIO_REG_PULL_MASK) >> LPI_GPIO_REG_PULL_SHIFT;
seq_printf(s, " %-8s: %-3s %d",
pindesc.name, is_out ? "out" : "in", func);
seq_printf(s, " %dmA", lpi_regval_to_drive(drive));
seq_printf(s, " %s", pulls[pull]);
}
static void lpi_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
{
unsigned int gpio = chip->base;
unsigned int i;
for (i = 0; i < chip->ngpio; i++, gpio++) {
lpi_gpio_dbg_show_one(s, NULL, chip, i, gpio);
seq_puts(s, "\n");
}
}
#else
#define lpi_gpio_dbg_show NULL
#endif
static const struct gpio_chip lpi_gpio_template = {
.direction_input = lpi_gpio_direction_input,
.direction_output = lpi_gpio_direction_output,
.get = lpi_gpio_get,
.set = lpi_gpio_set,
.request = gpiochip_generic_request,
.free = gpiochip_generic_free,
.dbg_show = lpi_gpio_dbg_show,
};
static int lpi_pinctrl_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pinctrl_pin_desc *pindesc;
struct pinctrl_desc *pctrldesc;
struct lpi_gpio_pad *pad, *pads;
struct lpi_gpio_state *state;
int ret, npins, i;
char __iomem *lpi_base;
u32 reg;
ret = of_property_read_u32(dev->of_node, "reg", &reg);
if (ret < 0) {
dev_err(dev, "missing base address\n");
return ret;
}
ret = of_property_read_u32(dev->of_node, "qcom,num-gpios", &npins);
if (ret < 0)
return ret;
WARN_ON(npins > ARRAY_SIZE(lpi_gpio_groups));
state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
platform_set_drvdata(pdev, state);
state->dev = &pdev->dev;
pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL);
if (!pindesc)
return -ENOMEM;
pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL);
if (!pads)
return -ENOMEM;
pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL);
if (!pctrldesc)
return -ENOMEM;
pctrldesc->pctlops = &lpi_gpio_pinctrl_ops;
pctrldesc->pmxops = &lpi_gpio_pinmux_ops;
pctrldesc->confops = &lpi_gpio_pinconf_ops;
pctrldesc->owner = THIS_MODULE;
pctrldesc->name = dev_name(dev);
pctrldesc->pins = pindesc;
pctrldesc->npins = npins;
lpi_base = devm_ioremap(dev, reg, LPI_ADDRESS_SIZE);
if (lpi_base == NULL) {
dev_err(dev, "%s devm_ioremap failed\n", __func__);
return -ENOMEM;
}
state->base = lpi_base;
for (i = 0; i < npins; i++, pindesc++) {
pad = &pads[i];
pindesc->drv_data = pad;
pindesc->number = i;
pindesc->name = lpi_gpio_groups[i];
pad->base = lpi_base;
pad->offset = lpi_offset[i];
}
state->chip = lpi_gpio_template;
state->chip.parent = dev;
state->chip.base = -1;
state->chip.ngpio = npins;
state->chip.label = dev_name(dev);
state->chip.of_gpio_n_cells = 2;
state->chip.can_sleep = false;
state->ctrl = devm_pinctrl_register(dev, pctrldesc, state);
if (IS_ERR(state->ctrl))
return PTR_ERR(state->ctrl);
ret = gpiochip_add_data(&state->chip, state);
if (ret) {
dev_err(state->dev, "can't add gpio chip\n");
goto err_chip;
}
ret = gpiochip_add_pin_range(&state->chip, dev_name(dev), 0, 0, npins);
if (ret) {
dev_err(dev, "failed to add pin range\n");
goto err_range;
}
lpi_dev_up = false;
ret = audio_notifier_register("lpi_tlmm", AUDIO_NOTIFIER_ADSP_DOMAIN,
&service_nb);
if (ret < 0) {
pr_err("%s: Audio notifier register failed ret = %d\n",
__func__, ret);
goto err_range;
}
return 0;
err_range:
gpiochip_remove(&state->chip);
err_chip:
return ret;
}
static int lpi_pinctrl_remove(struct platform_device *pdev)
{
struct lpi_gpio_state *state = platform_get_drvdata(pdev);
gpiochip_remove(&state->chip);
return 0;
}
static const struct of_device_id lpi_pinctrl_of_match[] = {
{ .compatible = "qcom,lpi-pinctrl" }, /* Generic */
{ },
};
MODULE_DEVICE_TABLE(of, lpi_pinctrl_of_match);
static struct platform_driver lpi_pinctrl_driver = {
.driver = {
.name = "qcom-lpi-pinctrl",
.of_match_table = lpi_pinctrl_of_match,
},
.probe = lpi_pinctrl_probe,
.remove = lpi_pinctrl_remove,
};
module_platform_driver(lpi_pinctrl_driver);
MODULE_DESCRIPTION("QTI LPI GPIO pin control driver");
MODULE_LICENSE("GPL v2");