284 lines
7.2 KiB
C
284 lines
7.2 KiB
C
// 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);
|
|
if (!g->regmap)
|
|
return -ENODEV;
|
|
|
|
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");
|