| /* |
| * 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", ®); |
| 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"); |