| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Author: Lorenzo Bianconi <lorenzo@kernel.org> | |
| * Author: Benjamin Larsson <benjamin.larsson@genexis.eu> | |
| * Author: Markus Gothe <markus.gothe@genexis.eu> | |
| */ | |
| #include "airoha-common.h" | |
| /* GPIOs */ | |
| #define REG_GPIO_CTRL 0x0000 | |
| #define REG_GPIO_DATA 0x0004 | |
| #define REG_GPIO_INT 0x0008 | |
| #define REG_GPIO_INT_EDGE 0x000c | |
| #define REG_GPIO_INT_LEVEL 0x0010 | |
| #define REG_GPIO_OE 0x0014 | |
| #define REG_GPIO_CTRL1 0x0020 | |
| #define REG_GPIO_CTRL2 0x0060 | |
| #define REG_GPIO_CTRL3 0x0064 | |
| #define REG_GPIO_DATA1 0x0070 | |
| #define REG_GPIO_OE1 0x0078 | |
| #define REG_GPIO_INT1 0x007c | |
| #define REG_GPIO_INT_EDGE1 0x0080 | |
| #define REG_GPIO_INT_EDGE2 0x0084 | |
| #define REG_GPIO_INT_EDGE3 0x0088 | |
| #define REG_GPIO_INT_LEVEL1 0x008c | |
| #define REG_GPIO_INT_LEVEL2 0x0090 | |
| #define REG_GPIO_INT_LEVEL3 0x0094 | |
| #define airoha_pinctrl_get_pullup_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_get_conf((pinctrl), AIROHA_PINCTRL_CONFS_PULLUP, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_get_pulldown_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_get_conf((pinctrl), AIROHA_PINCTRL_CONFS_PULLDOWN, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_get_drive_e2_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_get_conf((pinctrl), AIROHA_PINCTRL_CONFS_DRIVE_E2, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_get_drive_e4_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_get_conf((pinctrl), AIROHA_PINCTRL_CONFS_DRIVE_E4, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_get_pcie_rst_od_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_get_conf((pinctrl), AIROHA_PINCTRL_CONFS_PCIE_RST_OD, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_set_pullup_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_set_conf((pinctrl), AIROHA_PINCTRL_CONFS_PULLUP, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_set_pulldown_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_set_conf((pinctrl), AIROHA_PINCTRL_CONFS_PULLDOWN, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_set_drive_e2_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_set_conf((pinctrl), AIROHA_PINCTRL_CONFS_DRIVE_E2, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_set_drive_e4_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_set_conf((pinctrl), AIROHA_PINCTRL_CONFS_DRIVE_E4, \ | |
| (pin), (val)) | |
| #define airoha_pinctrl_set_pcie_rst_od_conf(pinctrl, pin, val) \ | |
| airoha_pinctrl_set_conf((pinctrl), AIROHA_PINCTRL_CONFS_PCIE_RST_OD, \ | |
| (pin), (val)) | |
| static const u32 gpio_data_regs[] = { | |
| REG_GPIO_DATA, | |
| REG_GPIO_DATA1 | |
| }; | |
| static const u32 gpio_out_regs[] = { | |
| REG_GPIO_OE, | |
| REG_GPIO_OE1 | |
| }; | |
| static const u32 gpio_dir_regs[] = { | |
| REG_GPIO_CTRL, | |
| REG_GPIO_CTRL1, | |
| REG_GPIO_CTRL2, | |
| REG_GPIO_CTRL3 | |
| }; | |
| static const u32 irq_status_regs[] = { | |
| REG_GPIO_INT, | |
| REG_GPIO_INT1 | |
| }; | |
| static const u32 irq_level_regs[] = { | |
| REG_GPIO_INT_LEVEL, | |
| REG_GPIO_INT_LEVEL1, | |
| REG_GPIO_INT_LEVEL2, | |
| REG_GPIO_INT_LEVEL3 | |
| }; | |
| static const u32 irq_edge_regs[] = { | |
| REG_GPIO_INT_EDGE, | |
| REG_GPIO_INT_EDGE1, | |
| REG_GPIO_INT_EDGE2, | |
| REG_GPIO_INT_EDGE3 | |
| }; | |
| static struct airoha_gpiochip_regs airoha_gpiochip_regs = { | |
| .data = gpio_data_regs, | |
| .dir = gpio_dir_regs, | |
| .out = gpio_out_regs, | |
| .status = irq_status_regs, | |
| .level = irq_level_regs, | |
| .edge = irq_edge_regs, | |
| }; | |
| static int airoha_convert_pin_to_reg_offset(struct pinctrl_dev *pctrl_dev, | |
| struct pinctrl_gpio_range *range, | |
| int pin) | |
| { | |
| if (!range) | |
| range = pinctrl_find_gpio_range_from_pin_nolock(pctrl_dev, | |
| pin); | |
| if (!range) | |
| return -EINVAL; | |
| return pin - range->pin_base; | |
| } | |
| /* gpio callbacks */ | |
| static int airoha_gpio_set(struct gpio_chip *chip, unsigned int gpio, | |
| int value) | |
| { | |
| struct airoha_pinctrl *pinctrl = gpiochip_get_data(chip); | |
| u32 offset = gpio % AIROHA_PIN_BANK_SIZE; | |
| u8 index = gpio / AIROHA_PIN_BANK_SIZE; | |
| return regmap_update_bits(pinctrl->regmap, | |
| pinctrl->gpio_regs->data[index], | |
| BIT(offset), value ? BIT(offset) : 0); | |
| } | |
| static int airoha_gpio_get(struct gpio_chip *chip, unsigned int gpio) | |
| { | |
| struct airoha_pinctrl *pinctrl = gpiochip_get_data(chip); | |
| u32 val, pin = gpio % AIROHA_PIN_BANK_SIZE; | |
| u8 index = gpio / AIROHA_PIN_BANK_SIZE; | |
| int err; | |
| err = regmap_read(pinctrl->regmap, | |
| pinctrl->gpio_regs->data[index], &val); | |
| return err ? err : !!(val & BIT(pin)); | |
| } | |
| static int airoha_gpio_get_direction(struct gpio_chip *chip, unsigned int gpio) | |
| { | |
| struct airoha_pinctrl *pinctrl = gpiochip_get_data(chip); | |
| u32 val, mask; | |
| u8 index; | |
| int err; | |
| index = gpio / AIROHA_REG_GPIOCTRL_NUM_PIN; | |
| err = regmap_read(pinctrl->regmap, | |
| pinctrl->gpio_regs->dir[index], &val); | |
| if (err) | |
| return err; | |
| mask = BIT(2 * (gpio % AIROHA_REG_GPIOCTRL_NUM_PIN)); | |
| return val & mask ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; | |
| } | |
| static int airoha_gpio_set_direction(struct gpio_chip *chip, unsigned int gpio, | |
| bool input) | |
| { | |
| struct airoha_pinctrl *pinctrl = gpiochip_get_data(chip); | |
| u32 mask, index; | |
| int err; | |
| /* set output enable */ | |
| mask = BIT(gpio % AIROHA_PIN_BANK_SIZE); | |
| index = gpio / AIROHA_PIN_BANK_SIZE; | |
| err = regmap_update_bits(pinctrl->regmap, | |
| pinctrl->gpio_regs->out[index], | |
| mask, !input ? mask : 0); | |
| if (err) | |
| return err; | |
| /* set direction */ | |
| mask = BIT(2 * (gpio % AIROHA_REG_GPIOCTRL_NUM_PIN)); | |
| index = gpio / AIROHA_REG_GPIOCTRL_NUM_PIN; | |
| return regmap_update_bits(pinctrl->regmap, | |
| pinctrl->gpio_regs->dir[index], mask, | |
| !input ? mask : 0); | |
| } | |
| static int airoha_gpio_direction_input(struct gpio_chip *chip, | |
| unsigned int gpio) | |
| { | |
| return airoha_gpio_set_direction(chip, gpio, true); | |
| } | |
| static int airoha_gpio_direction_output(struct gpio_chip *chip, | |
| unsigned int gpio, int value) | |
| { | |
| int err; | |
| err = airoha_gpio_set_direction(chip, gpio, false); | |
| if (err) | |
| return err; | |
| return airoha_gpio_set(chip, gpio, value); | |
| } | |
| /* irq callbacks */ | |
| static void airoha_irq_unmask(struct irq_data *data) | |
| { | |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(data); | |
| struct airoha_pinctrl *pinctrl = gpiochip_get_data(gc); | |
| struct airoha_gpiochip_regs *gpio_regs = pinctrl->gpio_regs; | |
| u8 offset = data->hwirq % AIROHA_REG_GPIOCTRL_NUM_PIN; | |
| u8 index = data->hwirq / AIROHA_REG_GPIOCTRL_NUM_PIN; | |
| u32 mask = GENMASK(2 * offset + 1, 2 * offset); | |
| u32 val = BIT(2 * offset); | |
| if (WARN_ON_ONCE(data->hwirq >= AIROHA_NUM_PINS)) | |
| return; | |
| gpiochip_enable_irq(gc, irqd_to_hwirq(data)); | |
| switch (irqd_get_trigger_type(data)) { | |
| case IRQ_TYPE_LEVEL_LOW: | |
| val = val << 1; | |
| fallthrough; | |
| case IRQ_TYPE_LEVEL_HIGH: | |
| regmap_update_bits(pinctrl->regmap, gpio_regs->level[index], | |
| mask, val); | |
| break; | |
| case IRQ_TYPE_EDGE_FALLING: | |
| val = val << 1; | |
| fallthrough; | |
| case IRQ_TYPE_EDGE_RISING: | |
| regmap_update_bits(pinctrl->regmap, gpio_regs->edge[index], | |
| mask, val); | |
| break; | |
| case IRQ_TYPE_EDGE_BOTH: | |
| regmap_set_bits(pinctrl->regmap, gpio_regs->edge[index], mask); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| static void airoha_irq_mask(struct irq_data *data) | |
| { | |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(data); | |
| struct airoha_pinctrl *pinctrl = gpiochip_get_data(gc); | |
| struct airoha_gpiochip_regs *gpio_regs = pinctrl->gpio_regs; | |
| u8 offset = data->hwirq % AIROHA_REG_GPIOCTRL_NUM_PIN; | |
| u8 index = data->hwirq / AIROHA_REG_GPIOCTRL_NUM_PIN; | |
| u32 mask = GENMASK(2 * offset + 1, 2 * offset); | |
| if (data->hwirq >= AIROHA_NUM_PINS) | |
| return; | |
| regmap_clear_bits(pinctrl->regmap, gpio_regs->level[index], mask); | |
| regmap_clear_bits(pinctrl->regmap, gpio_regs->edge[index], mask); | |
| gpiochip_disable_irq(gc, irqd_to_hwirq(data)); | |
| } | |
| static void airoha_irq_ack(struct irq_data *data) | |
| { | |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(data); | |
| struct airoha_pinctrl *pinctrl = gpiochip_get_data(gc); | |
| struct airoha_gpiochip_regs *gpio_regs = pinctrl->gpio_regs; | |
| u8 offset = data->hwirq % AIROHA_PIN_BANK_SIZE; | |
| u8 index = data->hwirq / AIROHA_PIN_BANK_SIZE; | |
| if (data->hwirq >= AIROHA_NUM_PINS) | |
| return; | |
| regmap_write(pinctrl->regmap, gpio_regs->status[index], BIT(offset)); | |
| } | |
| static int airoha_irq_type(struct irq_data *data, unsigned int type) | |
| { | |
| if (data->hwirq >= AIROHA_NUM_PINS) | |
| return -EINVAL; | |
| if (type == IRQ_TYPE_NONE) { | |
| irqd_set_trigger_type(data, type); | |
| irq_set_handler_locked(data, handle_bad_irq); | |
| return 0; | |
| } | |
| if (type == IRQ_TYPE_PROBE) { | |
| if (irqd_get_trigger_type(data)) | |
| return 0; | |
| type = IRQ_TYPE_EDGE_BOTH; | |
| } | |
| irqd_set_trigger_type(data, type); | |
| if (type & IRQ_TYPE_EDGE_BOTH) | |
| irq_set_handler_locked(data, handle_edge_irq); | |
| else | |
| irq_set_handler_locked(data, handle_level_irq); | |
| return 0; | |
| } | |
| static irqreturn_t airoha_irq_handler(int irq, void *data) | |
| { | |
| struct airoha_pinctrl *pinctrl = data; | |
| bool handled = false; | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(irq_status_regs); i++) { | |
| struct gpio_irq_chip *girq = &pinctrl->gpiochip.irq; | |
| u32 regmap; | |
| unsigned long status; | |
| int irq; | |
| if (regmap_read(pinctrl->regmap, pinctrl->gpio_regs->status[i], | |
| ®map)) | |
| continue; | |
| status = regmap; | |
| for_each_set_bit(irq, &status, AIROHA_PIN_BANK_SIZE) { | |
| u32 offset = irq + i * AIROHA_PIN_BANK_SIZE; | |
| generic_handle_domain_irq(girq->domain, offset); | |
| regmap_write(pinctrl->regmap, | |
| pinctrl->gpio_regs->status[i], BIT(irq)); | |
| } | |
| handled |= !!status; | |
| } | |
| return handled ? IRQ_HANDLED : IRQ_NONE; | |
| } | |
| static const struct irq_chip airoha_gpio_irq_chip = { | |
| .name = "airoha-gpio-irq", | |
| .irq_unmask = airoha_irq_unmask, | |
| .irq_mask = airoha_irq_mask, | |
| .irq_ack = airoha_irq_ack, | |
| .irq_set_type = airoha_irq_type, | |
| .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_IMMUTABLE, | |
| GPIOCHIP_IRQ_RESOURCE_HELPERS, | |
| }; | |
| static int airoha_pinctrl_add_gpiochip(struct airoha_pinctrl *pinctrl, | |
| struct platform_device *pdev) | |
| { | |
| struct gpio_chip *gc = &pinctrl->gpiochip; | |
| struct gpio_irq_chip *girq = &gc->irq; | |
| struct device *dev = &pdev->dev; | |
| int irq, err; | |
| gc->parent = dev; | |
| gc->label = dev_name(dev); | |
| gc->request = gpiochip_generic_request; | |
| gc->free = gpiochip_generic_free; | |
| gc->direction_input = airoha_gpio_direction_input; | |
| gc->direction_output = airoha_gpio_direction_output; | |
| gc->get_direction = airoha_gpio_get_direction; | |
| gc->set = airoha_gpio_set; | |
| gc->get = airoha_gpio_get; | |
| gc->base = -1; | |
| gc->ngpio = AIROHA_NUM_PINS; | |
| girq->default_type = IRQ_TYPE_NONE; | |
| girq->handler = handle_bad_irq; | |
| gpio_irq_chip_set_chip(girq, &airoha_gpio_irq_chip); | |
| irq = platform_get_irq(pdev, 0); | |
| if (irq < 0) | |
| return irq; | |
| err = devm_request_irq(dev, irq, airoha_irq_handler, IRQF_SHARED, | |
| dev_name(dev), pinctrl); | |
| if (err) | |
| return err; | |
| return devm_gpiochip_add_data(dev, gc, pinctrl); | |
| } | |
| /* pinmux callbacks */ | |
| static int airoha_pinmux_set_mux(struct pinctrl_dev *pctrl_dev, | |
| unsigned int selector, | |
| unsigned int group) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| const struct airoha_pinctrl_func *func; | |
| const struct function_desc *desc; | |
| struct group_desc *grp; | |
| int i; | |
| desc = pinmux_generic_get_function(pctrl_dev, selector); | |
| if (!desc) | |
| return -EINVAL; | |
| grp = pinctrl_generic_get_group(pctrl_dev, group); | |
| if (!grp) | |
| return -EINVAL; | |
| dev_dbg(pctrl_dev->dev, "enable function %s group %s\n", | |
| desc->func->name, grp->grp.name); | |
| func = desc->data; | |
| for (i = 0; i < func->group_size; i++) { | |
| const struct airoha_pinctrl_func_group *group; | |
| int j; | |
| group = &func->groups[i]; | |
| if (strcmp(group->name, grp->grp.name)) | |
| continue; | |
| for (j = 0; j < group->regmap_size; j++) { | |
| switch (group->regmap[j].mux) { | |
| case AIROHA_FUNC_PWM_EXT_MUX: | |
| case AIROHA_FUNC_PWM_MUX: | |
| regmap_update_bits(pinctrl->regmap, | |
| group->regmap[j].offset, | |
| group->regmap[j].mask, | |
| group->regmap[j].val); | |
| break; | |
| default: | |
| regmap_update_bits(pinctrl->chip_scu, | |
| group->regmap[j].offset, | |
| group->regmap[j].mask, | |
| group->regmap[j].val); | |
| break; | |
| } | |
| } | |
| return 0; | |
| } | |
| return -EINVAL; | |
| } | |
| static int airoha_pinmux_set_direction(struct pinctrl_dev *pctrl_dev, | |
| struct pinctrl_gpio_range *range, | |
| unsigned int p, bool input) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| int pin; | |
| pin = airoha_convert_pin_to_reg_offset(pctrl_dev, range, p); | |
| if (pin < 0) | |
| return pin; | |
| return airoha_gpio_set_direction(&pinctrl->gpiochip, pin, input); | |
| } | |
| static const struct pinmux_ops airoha_pmxops = { | |
| .get_functions_count = pinmux_generic_get_function_count, | |
| .get_function_name = pinmux_generic_get_function_name, | |
| .get_function_groups = pinmux_generic_get_function_groups, | |
| .gpio_set_direction = airoha_pinmux_set_direction, | |
| .set_mux = airoha_pinmux_set_mux, | |
| .strict = true, | |
| }; | |
| /* pinconf callbacks */ | |
| static const struct airoha_pinctrl_reg * | |
| airoha_pinctrl_get_conf_reg(const struct airoha_pinctrl_conf *conf, | |
| int conf_size, int pin) | |
| { | |
| int i; | |
| for (i = 0; i < conf_size; i++) { | |
| if (conf[i].pin == pin) | |
| return &conf[i].reg; | |
| } | |
| return NULL; | |
| } | |
| static int airoha_pinctrl_get_conf(struct airoha_pinctrl *pinctrl, | |
| enum airoha_pinctrl_confs_type conf_type, | |
| int pin, u32 *val) | |
| { | |
| const struct airoha_pinctrl_confs_info *confs_info; | |
| const struct airoha_pinctrl_reg *reg; | |
| confs_info = &pinctrl->confs_info[conf_type]; | |
| reg = airoha_pinctrl_get_conf_reg(confs_info->confs, | |
| confs_info->num_confs, | |
| pin); | |
| if (!reg) | |
| return -EINVAL; | |
| if (regmap_read(pinctrl->chip_scu, reg->offset, val)) | |
| return -EINVAL; | |
| *val = field_get(reg->mask, *val); | |
| return 0; | |
| } | |
| static int airoha_pinctrl_set_conf(struct airoha_pinctrl *pinctrl, | |
| enum airoha_pinctrl_confs_type conf_type, | |
| int pin, u32 val) | |
| { | |
| const struct airoha_pinctrl_confs_info *confs_info; | |
| const struct airoha_pinctrl_reg *reg = NULL; | |
| confs_info = &pinctrl->confs_info[conf_type]; | |
| reg = airoha_pinctrl_get_conf_reg(confs_info->confs, | |
| confs_info->num_confs, | |
| pin); | |
| if (!reg) | |
| return -EINVAL; | |
| if (regmap_update_bits(pinctrl->chip_scu, reg->offset, reg->mask, | |
| field_prep(reg->mask, val))) | |
| return -EINVAL; | |
| return 0; | |
| } | |
| static int airoha_pinconf_get_direction(struct pinctrl_dev *pctrl_dev, u32 p) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| int ret, pin; | |
| pin = airoha_convert_pin_to_reg_offset(pctrl_dev, NULL, p); | |
| if (pin < 0) | |
| return pin; | |
| ret = airoha_gpio_get_direction(&pinctrl->gpiochip, pin); | |
| if (ret < 0) | |
| return ret; | |
| return ret == GPIO_LINE_DIRECTION_OUT ? | |
| PIN_CONFIG_OUTPUT_ENABLE : PIN_CONFIG_INPUT_ENABLE; | |
| } | |
| static int airoha_pinconf_get(struct pinctrl_dev *pctrl_dev, | |
| unsigned int pin, unsigned long *config) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| enum pin_config_param param = pinconf_to_config_param(*config); | |
| u32 arg; | |
| switch (param) { | |
| case PIN_CONFIG_BIAS_PULL_DOWN: | |
| case PIN_CONFIG_BIAS_DISABLE: | |
| case PIN_CONFIG_BIAS_PULL_UP: { | |
| u32 pull_up, pull_down; | |
| if (airoha_pinctrl_get_pullup_conf(pinctrl, pin, &pull_up) || | |
| airoha_pinctrl_get_pulldown_conf(pinctrl, pin, &pull_down)) | |
| return -EINVAL; | |
| if (param == PIN_CONFIG_BIAS_PULL_UP && | |
| !(pull_up && !pull_down)) | |
| return -EINVAL; | |
| else if (param == PIN_CONFIG_BIAS_PULL_DOWN && | |
| !(pull_down && !pull_up)) | |
| return -EINVAL; | |
| else if (pull_up || pull_down) | |
| return -EINVAL; | |
| arg = 1; | |
| break; | |
| } | |
| case PIN_CONFIG_DRIVE_STRENGTH: { | |
| u32 e2, e4; | |
| if (airoha_pinctrl_get_drive_e2_conf(pinctrl, pin, &e2) || | |
| airoha_pinctrl_get_drive_e4_conf(pinctrl, pin, &e4)) | |
| return -EINVAL; | |
| arg = e4 << 1 | e2; | |
| break; | |
| } | |
| case PIN_CONFIG_DRIVE_OPEN_DRAIN: | |
| if (airoha_pinctrl_get_pcie_rst_od_conf(pinctrl, pin, &arg)) | |
| return -EINVAL; | |
| break; | |
| case PIN_CONFIG_OUTPUT_ENABLE: | |
| case PIN_CONFIG_INPUT_ENABLE: | |
| arg = airoha_pinconf_get_direction(pctrl_dev, pin); | |
| if (arg != param) | |
| return -EINVAL; | |
| arg = 1; | |
| break; | |
| default: | |
| return -ENOTSUPP; | |
| } | |
| *config = pinconf_to_config_packed(param, arg); | |
| return 0; | |
| } | |
| static int airoha_pinconf_set_pin_value(struct pinctrl_dev *pctrl_dev, | |
| unsigned int p, bool value) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| int pin; | |
| pin = airoha_convert_pin_to_reg_offset(pctrl_dev, NULL, p); | |
| if (pin < 0) | |
| return pin; | |
| return airoha_gpio_set(&pinctrl->gpiochip, pin, value); | |
| } | |
| static int airoha_pinconf_set(struct pinctrl_dev *pctrl_dev, | |
| unsigned int pin, unsigned long *configs, | |
| unsigned int num_configs) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| int i, err; | |
| for (i = 0; i < num_configs; i++) { | |
| u32 param = pinconf_to_config_param(configs[i]); | |
| u32 arg = pinconf_to_config_argument(configs[i]); | |
| switch (param) { | |
| case PIN_CONFIG_BIAS_DISABLE: | |
| err = airoha_pinctrl_set_pulldown_conf(pinctrl, pin, 0); | |
| if (err) | |
| return err; | |
| err = airoha_pinctrl_set_pullup_conf(pinctrl, pin, 0); | |
| if (err) | |
| return err; | |
| break; | |
| case PIN_CONFIG_BIAS_PULL_UP: | |
| err = airoha_pinctrl_set_pulldown_conf(pinctrl, pin, 0); | |
| if (err) | |
| return err; | |
| err = airoha_pinctrl_set_pullup_conf(pinctrl, pin, 1); | |
| if (err) | |
| return err; | |
| break; | |
| case PIN_CONFIG_BIAS_PULL_DOWN: | |
| err = airoha_pinctrl_set_pulldown_conf(pinctrl, pin, 1); | |
| if (err) | |
| return err; | |
| err = airoha_pinctrl_set_pullup_conf(pinctrl, pin, 0); | |
| if (err) | |
| return err; | |
| break; | |
| case PIN_CONFIG_DRIVE_STRENGTH: { | |
| u32 e2 = 0, e4 = 0; | |
| switch (arg) { | |
| case MTK_DRIVE_2mA: | |
| break; | |
| case MTK_DRIVE_4mA: | |
| e2 = 1; | |
| break; | |
| case MTK_DRIVE_6mA: | |
| e4 = 1; | |
| break; | |
| case MTK_DRIVE_8mA: | |
| e2 = 1; | |
| e4 = 1; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| err = airoha_pinctrl_set_drive_e2_conf(pinctrl, | |
| pin, e2); | |
| if (err) | |
| return err; | |
| err = airoha_pinctrl_set_drive_e4_conf(pinctrl, | |
| pin, e4); | |
| if (err) | |
| return err; | |
| break; | |
| } | |
| case PIN_CONFIG_DRIVE_OPEN_DRAIN: | |
| err = airoha_pinctrl_set_pcie_rst_od_conf(pinctrl, | |
| pin, !!arg); | |
| if (err) | |
| return err; | |
| break; | |
| case PIN_CONFIG_OUTPUT_ENABLE: | |
| case PIN_CONFIG_INPUT_ENABLE: | |
| case PIN_CONFIG_LEVEL: { | |
| bool input = param == PIN_CONFIG_INPUT_ENABLE; | |
| err = airoha_pinmux_set_direction(pctrl_dev, NULL, pin, | |
| input); | |
| if (err) | |
| return err; | |
| if (param == PIN_CONFIG_LEVEL) { | |
| err = airoha_pinconf_set_pin_value(pctrl_dev, | |
| pin, !!arg); | |
| if (err) | |
| return err; | |
| } | |
| break; | |
| } | |
| default: | |
| return -ENOTSUPP; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int airoha_pinconf_group_get(struct pinctrl_dev *pctrl_dev, | |
| unsigned int group, unsigned long *config) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| unsigned long cur_config = 0; | |
| int i; | |
| for (i = 0; i < pinctrl->grps[group].npins; i++) { | |
| if (airoha_pinconf_get(pctrl_dev, | |
| pinctrl->grps[group].pins[i], | |
| config)) | |
| return -ENOTSUPP; | |
| if (i && cur_config != *config) | |
| return -ENOTSUPP; | |
| cur_config = *config; | |
| } | |
| return 0; | |
| } | |
| static int airoha_pinconf_group_set(struct pinctrl_dev *pctrl_dev, | |
| unsigned int group, unsigned long *configs, | |
| unsigned int num_configs) | |
| { | |
| struct airoha_pinctrl *pinctrl = pinctrl_dev_get_drvdata(pctrl_dev); | |
| int i; | |
| for (i = 0; i < pinctrl->grps[group].npins; i++) { | |
| int err; | |
| err = airoha_pinconf_set(pctrl_dev, | |
| pinctrl->grps[group].pins[i], | |
| configs, num_configs); | |
| if (err) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static const struct pinconf_ops airoha_confops = { | |
| .is_generic = true, | |
| .pin_config_get = airoha_pinconf_get, | |
| .pin_config_set = airoha_pinconf_set, | |
| .pin_config_group_get = airoha_pinconf_group_get, | |
| .pin_config_group_set = airoha_pinconf_group_set, | |
| .pin_config_config_dbg_show = pinconf_generic_dump_config, | |
| }; | |
| static const struct pinctrl_ops airoha_pctlops = { | |
| .get_groups_count = pinctrl_generic_get_group_count, | |
| .get_group_name = pinctrl_generic_get_group_name, | |
| .get_group_pins = pinctrl_generic_get_group_pins, | |
| .dt_node_to_map = pinconf_generic_dt_node_to_map_all, | |
| .dt_free_map = pinconf_generic_dt_free_map, | |
| }; | |
| int airoha_pinctrl_probe(struct platform_device *pdev) | |
| { | |
| const struct airoha_pinctrl_match_data *data; | |
| struct device *dev = &pdev->dev; | |
| struct airoha_pinctrl *pinctrl; | |
| struct regmap *map; | |
| int err, i; | |
| data = device_get_match_data(dev); | |
| if (!data) | |
| return -EINVAL; | |
| pinctrl = devm_kzalloc(dev, sizeof(*pinctrl), GFP_KERNEL); | |
| if (!pinctrl) | |
| return -ENOMEM; | |
| pinctrl->regmap = device_node_to_regmap(dev->parent->of_node); | |
| if (IS_ERR(pinctrl->regmap)) | |
| return PTR_ERR(pinctrl->regmap); | |
| map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "airoha,chip-scu"); | |
| if (IS_ERR_OR_NULL(map)) { | |
| map = syscon_regmap_lookup_by_compatible(data->chip_scu_compatible); | |
| if (IS_ERR(map)) | |
| return PTR_ERR(map); | |
| } | |
| pinctrl->chip_scu = map; | |
| /* Init pinctrl desc struct */ | |
| pinctrl->desc.name = data->pinctrl_name; | |
| pinctrl->desc.owner = data->pinctrl_owner; | |
| pinctrl->desc.pctlops = &airoha_pctlops; | |
| pinctrl->desc.pmxops = &airoha_pmxops; | |
| pinctrl->desc.confops = &airoha_confops; | |
| pinctrl->desc.pins = data->pins; | |
| pinctrl->desc.npins = data->num_pins; | |
| pinctrl->gpio_regs = &airoha_gpiochip_regs; | |
| err = devm_pinctrl_register_and_init(dev, &pinctrl->desc, | |
| pinctrl, &pinctrl->ctrl); | |
| if (err) | |
| return err; | |
| /* build pin groups */ | |
| for (i = 0; i < data->num_grps; i++) { | |
| const struct pingroup *grp = &data->grps[i]; | |
| err = pinctrl_generic_add_group(pinctrl->ctrl, grp->name, | |
| grp->pins, grp->npins, | |
| (void *)grp); | |
| if (err < 0) { | |
| dev_err(&pdev->dev, "Failed to register group %s\n", | |
| grp->name); | |
| return err; | |
| } | |
| } | |
| /* build functions */ | |
| for (i = 0; i < data->num_funcs; i++) { | |
| const struct airoha_pinctrl_func *func; | |
| func = &data->funcs[i]; | |
| err = pinmux_generic_add_pinfunction(pinctrl->ctrl, | |
| &func->desc, | |
| (void *)func); | |
| if (err < 0) { | |
| dev_err(dev, "Failed to register function %s\n", | |
| func->desc.name); | |
| return err; | |
| } | |
| } | |
| pinctrl->grps = data->grps; | |
| pinctrl->funcs = data->funcs; | |
| pinctrl->confs_info = data->confs_info; | |
| err = pinctrl_enable(pinctrl->ctrl); | |
| if (err) | |
| return err; | |
| /* build gpio-chip */ | |
| return airoha_pinctrl_add_gpiochip(pinctrl, pdev); | |
| } | |
| EXPORT_SYMBOL_GPL(airoha_pinctrl_probe); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_AUTHOR("Lorenzo Bianconi <lorenzo@kernel.org>"); | |
| MODULE_AUTHOR("Benjamin Larsson <benjamin.larsson@genexis.eu>"); | |
| MODULE_AUTHOR("Markus Gothe <markus.gothe@genexis.eu>"); | |
| MODULE_DESCRIPTION("Pinctrl common driver for Airoha SoC"); |