/* * 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 #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "pinctrl-utils.h" #define LPI_ADDRESS_SIZE 0x20000 #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 { u32 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, 0x00004000, 0x00005000, 0x00006000, 0x00007000, 0x00008000, 0x00009000, 0x0000A000, 0x0000B000, 0x0000C000, 0x0000D000, 0x0000E000, 0x0000F000, 0x00010000, 0x00011000, 0x00012000, 0x00013000, 0x00014000, 0x00015000, 0x00016000, 0x00017000, 0x00018000, 0x00019000, 0x0001A000, 0x0001B000, 0x0001C000, 0x0001D000, 0x0001E000, 0x0001F000, }; 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) { static bool initial_boot = true; pr_debug("%s: Service opcode 0x%lx\n", __func__, opcode); switch (opcode) { case AUDIO_NOTIFIER_SERVICE_DOWN: if (initial_boot) { initial_boot = false; break; } lpi_dev_up = false; break; case AUDIO_NOTIFIER_SERVICE_UP: if (initial_boot) initial_boot = false; 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 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 lpi_gpio_state *state = gpiochip_get_data(chip); 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" }; pctldev = pctldev ? : state->ctrl; 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 = true; 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); audio_notifier_deregister("lpi_tlmm"); 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");