| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Support to GPIOs on ROHM BD72720 and BD79300 |
| * Copyright 2025 ROHM Semiconductors. |
| * Author: Matti Vaittinen <mazziesaccount@gmail.com> |
| */ |
| |
| #include <linux/gpio/driver.h> |
| #include <linux/init.h> |
| #include <linux/irq.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/mfd/rohm-bd72720.h> |
| |
| #define BD72720_GPIO_OPEN_DRAIN 0 |
| #define BD72720_GPIO_CMOS BIT(1) |
| #define BD72720_INT_GPIO1_IN_SRC 4 |
| /* |
| * The BD72720 has several "one time programmable" (OTP) configurations which |
| * can be set at manufacturing phase. A set of these options allow using pins |
| * as GPIO. The OTP configuration can't be read at run-time, so drivers rely on |
| * device-tree to advertise the correct options. |
| * |
| * Both DVS[0,1] pins can be configured to be used for: |
| * - OTP0: regulator RUN state control |
| * - OTP1: GPI |
| * - OTP2: GPO |
| * - OTP3: Power sequencer output |
| * Data-sheet also states that these PINs can always be used for IRQ but the |
| * driver limits this by allowing them to be used for IRQs with OTP1 only. |
| * |
| * Pins GPIO_EXTEN0 (GPIO3), GPIO_EXTEN1 (GPIO4), GPIO_FAULT_B (GPIO5) have OTP |
| * options for a specific (non GPIO) purposes, but also an option to configure |
| * them to be used as a GPO. |
| * |
| * OTP settings can be separately configured for each pin. |
| * |
| * DT properties: |
| * "rohm,pin-dvs0" and "rohm,pin-dvs1" can be set to one of the values: |
| * "dvs-input", "gpi", "gpo". |
| * |
| * "rohm,pin-exten0", "rohm,pin-exten1" and "rohm,pin-fault_b" can be set to: |
| * "gpo" |
| */ |
| |
| enum bd72720_gpio_state { |
| BD72720_PIN_UNKNOWN, |
| BD72720_PIN_GPI, |
| BD72720_PIN_GPO, |
| }; |
| |
| enum { |
| BD72720_GPIO1, |
| BD72720_GPIO2, |
| BD72720_GPIO3, |
| BD72720_GPIO4, |
| BD72720_GPIO5, |
| BD72720_GPIO_EPDEN, |
| BD72720_NUM_GPIOS |
| }; |
| |
| struct bd72720_gpio { |
| /* chip.parent points the MFD which provides DT node and regmap */ |
| struct gpio_chip chip; |
| /* dev points to the platform device for devm and prints */ |
| struct device *dev; |
| struct regmap *regmap; |
| int gpio_is_input; |
| }; |
| |
| static int bd72720gpi_get(struct bd72720_gpio *bdgpio, unsigned int reg_offset) |
| { |
| int ret, val, shift; |
| |
| ret = regmap_read(bdgpio->regmap, BD72720_REG_INT_ETC1_SRC, &val); |
| if (ret) |
| return ret; |
| |
| shift = BD72720_INT_GPIO1_IN_SRC + reg_offset; |
| |
| return (val >> shift) & 1; |
| } |
| |
| static int bd72720gpo_get(struct bd72720_gpio *bdgpio, |
| unsigned int offset) |
| { |
| const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, |
| BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, |
| BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL }; |
| int ret, val; |
| |
| ret = regmap_read(bdgpio->regmap, regs[offset], &val); |
| if (ret) |
| return ret; |
| |
| return val & BD72720_GPIO_HIGH; |
| } |
| |
| static int bd72720gpio_get(struct gpio_chip *chip, unsigned int offset) |
| { |
| struct bd72720_gpio *bdgpio = gpiochip_get_data(chip); |
| |
| if (BIT(offset) & bdgpio->gpio_is_input) |
| return bd72720gpi_get(bdgpio, offset); |
| |
| return bd72720gpo_get(bdgpio, offset); |
| } |
| |
| static int bd72720gpo_set(struct gpio_chip *chip, unsigned int offset, |
| int value) |
| { |
| struct bd72720_gpio *bdgpio = gpiochip_get_data(chip); |
| const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, |
| BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, |
| BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL }; |
| |
| if (BIT(offset) & bdgpio->gpio_is_input) { |
| dev_dbg(bdgpio->dev, "pin %d not output.\n", offset); |
| return -EINVAL; |
| } |
| |
| if (value) |
| return regmap_set_bits(bdgpio->regmap, regs[offset], |
| BD72720_GPIO_HIGH); |
| |
| return regmap_clear_bits(bdgpio->regmap, regs[offset], |
| BD72720_GPIO_HIGH); |
| } |
| |
| static int bd72720_gpio_set_config(struct gpio_chip *chip, unsigned int offset, |
| unsigned long config) |
| { |
| struct bd72720_gpio *bdgpio = gpiochip_get_data(chip); |
| const int regs[] = { BD72720_REG_GPIO1_CTRL, BD72720_REG_GPIO2_CTRL, |
| BD72720_REG_GPIO3_CTRL, BD72720_REG_GPIO4_CTRL, |
| BD72720_REG_GPIO5_CTRL, BD72720_REG_EPDEN_CTRL }; |
| |
| /* |
| * We can only set the output mode, which makes sense only when output |
| * OTP configuration is used. |
| */ |
| if (BIT(offset) & bdgpio->gpio_is_input) |
| return -ENOTSUPP; |
| |
| switch (pinconf_to_config_param(config)) { |
| case PIN_CONFIG_DRIVE_OPEN_DRAIN: |
| return regmap_update_bits(bdgpio->regmap, |
| regs[offset], |
| BD72720_GPIO_DRIVE_MASK, |
| BD72720_GPIO_OPEN_DRAIN); |
| case PIN_CONFIG_DRIVE_PUSH_PULL: |
| return regmap_update_bits(bdgpio->regmap, |
| regs[offset], |
| BD72720_GPIO_DRIVE_MASK, |
| BD72720_GPIO_CMOS); |
| default: |
| break; |
| } |
| |
| return -ENOTSUPP; |
| } |
| |
| static int bd72720gpo_direction_get(struct gpio_chip *chip, |
| unsigned int offset) |
| { |
| struct bd72720_gpio *bdgpio = gpiochip_get_data(chip); |
| |
| if (BIT(offset) & bdgpio->gpio_is_input) |
| return GPIO_LINE_DIRECTION_IN; |
| |
| return GPIO_LINE_DIRECTION_OUT; |
| } |
| |
| static int bd72720_valid_mask(struct gpio_chip *gc, |
| unsigned long *valid_mask, |
| unsigned int ngpios) |
| { |
| static const char * const properties[] = { |
| "rohm,pin-dvs0", "rohm,pin-dvs1", "rohm,pin-exten0", |
| "rohm,pin-exten1", "rohm,pin-fault_b" |
| }; |
| struct bd72720_gpio *g = gpiochip_get_data(gc); |
| const char *val; |
| int i, ret; |
| |
| *valid_mask = BIT(BD72720_GPIO_EPDEN); |
| |
| if (!gc->parent) |
| return 0; |
| |
| for (i = 0; i < ARRAY_SIZE(properties); i++) { |
| ret = fwnode_property_read_string(dev_fwnode(gc->parent), |
| properties[i], &val); |
| |
| if (ret) { |
| if (ret == -EINVAL) |
| continue; |
| |
| dev_err(g->dev, "pin %d (%s), bad configuration\n", i, |
| properties[i]); |
| |
| return ret; |
| } |
| |
| if (strcmp(val, "gpi") == 0) { |
| if (i != BD72720_GPIO1 && i != BD72720_GPIO2) { |
| dev_warn(g->dev, |
| "pin %d (%s) does not support INPUT mode", |
| i, properties[i]); |
| continue; |
| } |
| |
| *valid_mask |= BIT(i); |
| g->gpio_is_input |= BIT(i); |
| } else if (strcmp(val, "gpo") == 0) { |
| *valid_mask |= BIT(i); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Template for GPIO chip */ |
| static const struct gpio_chip bd72720gpo_chip = { |
| .label = "bd72720", |
| .owner = THIS_MODULE, |
| .get = bd72720gpio_get, |
| .get_direction = bd72720gpo_direction_get, |
| .set = bd72720gpo_set, |
| .set_config = bd72720_gpio_set_config, |
| .init_valid_mask = bd72720_valid_mask, |
| .can_sleep = true, |
| .ngpio = BD72720_NUM_GPIOS, |
| .base = -1, |
| }; |
| |
| static int gpo_bd72720_probe(struct platform_device *pdev) |
| { |
| struct bd72720_gpio *g; |
| struct device *parent, *dev; |
| |
| /* |
| * Bind devm lifetime to this platform device => use dev for devm. |
| * also the prints should originate from this device. |
| */ |
| dev = &pdev->dev; |
| /* The device-tree and regmap come from MFD => use parent for that */ |
| parent = dev->parent; |
| |
| g = devm_kzalloc(dev, sizeof(*g), GFP_KERNEL); |
| if (!g) |
| return -ENOMEM; |
| |
| g->chip = bd72720gpo_chip; |
| g->dev = dev; |
| g->chip.parent = parent; |
| g->regmap = dev_get_regmap(parent, NULL); |
| |
| return devm_gpiochip_add_data(dev, &g->chip, g); |
| } |
| |
| static const struct platform_device_id bd72720_gpio_id[] = { |
| { "bd72720-gpio" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(platform, bd72720_gpio_id); |
| |
| static struct platform_driver gpo_bd72720_driver = { |
| .driver = { |
| .name = "bd72720-gpio", |
| .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
| }, |
| .probe = gpo_bd72720_probe, |
| .id_table = bd72720_gpio_id, |
| }; |
| module_platform_driver(gpo_bd72720_driver); |
| |
| MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>"); |
| MODULE_DESCRIPTION("GPIO interface for BD72720 and BD73900"); |
| MODULE_LICENSE("GPL"); |