[PATCH v6 6/7] pinctrl: s32cc: implement GPIO functionality

Andrei Stefanescu
2024-11-13T10:12:48Z

Add basic GPIO functionality (request, free, get, set) for the existing
pinctrl SIUL2 driver since the hardware for pinctrl&GPIO is tightly
coupled.

Also, remove pinmux_ops which are no longer needed.

Signed-off-by: Andrei Stefanescu <andrei.stefanescu@oss.nxp.com>
---
drivers/pinctrl/nxp/pinctrl-s32cc.c | 406 +++++++++++++++++++++++-----
1 file changed, 344 insertions(+), 62 deletions(-)

diff --git a/drivers/pinctrl/nxp/pinctrl-s32cc.c b/drivers/pinctrl/nxp/pinctrl-s32cc.c
index 0b79c7445929..e213a910d958 100644
--- a/drivers/pinctrl/nxp/pinctrl-s32cc.c
+++ b/drivers/pinctrl/nxp/pinctrl-s32cc.c
@@ -40,6 +40,14 @@
#define S32_MSCR_ODE BIT(20)
#define S32_MSCR_OBE BIT(21)

+/* PGPDOs are 16bit registers that come in big endian
+ * order if they are grouped in pairs of two.
+ *
+ * For example, the order is PGPDO1, PGPDO0, PGPDO3, PGPDO2...
+ */
+#define S32_PGPD(N) (((N) ^ 1) * 2)
+#define S32_PGPD_SIZE 16
+
enum s32_write_type {
S32_PINCONF_UPDATE_ONLY,
S32_PINCONF_OVERWRITE,
@@ -84,6 +92,7 @@ struct s32_pinctrl_context {
* struct s32_pinctrl - private driver data
* @dev: a pointer back to containing device
* @pctl: a pointer to the pinctrl device structure
+ * @gc: a pointer to the gpio_chip
* @regions: reserved memory regions with start/end pin
* @info: structure containing information about the pin
* @gpio_configs: Saved configurations for GPIO pins
@@ -93,6 +102,7 @@ struct s32_pinctrl_context {
struct s32_pinctrl {
struct device *dev;
struct pinctrl_dev *pctl;
+ struct gpio_chip gc;
struct s32_pinctrl_mem_region *regions;
struct s32_pinctrl_soc_info *info;
struct list_head gpio_configs;
@@ -366,66 +376,6 @@ static int s32_pmx_get_groups(struct pinctrl_dev *pctldev,
return 0;
}

-static int s32_pmx_gpio_request_enable(struct pinctrl_dev *pctldev,
- struct pinctrl_gpio_range *range,
- unsigned int offset)
-{
- struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
- struct gpio_pin_config *gpio_pin;
- unsigned int config;
- unsigned long flags;
- int ret;
-
- ret = s32_regmap_read(pctldev, offset, &config);
- if (ret)
- return ret;
-
- /* Save current configuration */
- gpio_pin = kmalloc(sizeof(*gpio_pin), GFP_KERNEL);
- if (!gpio_pin)
- return -ENOMEM;
-
- gpio_pin->pin_id = offset;
- gpio_pin->config = config;
-
- spin_lock_irqsave(&ipctl->gpio_configs_lock, flags);
- list_add(&gpio_pin->list, &ipctl->gpio_configs);
- spin_unlock_irqrestore(&ipctl->gpio_configs_lock, flags);
-
- /* GPIO pin means SSS = 0 */
- config &= ~S32_MSCR_SSS_MASK;
-
- return s32_regmap_write(pctldev, offset, config);
-}
-
-static void s32_pmx_gpio_disable_free(struct pinctrl_dev *pctldev,
- struct pinctrl_gpio_range *range,
- unsigned int offset)
-{
- struct s32_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
- struct gpio_pin_config *gpio_pin, *tmp;
- unsigned long flags;
- int ret;
-
- spin_lock_irqsave(&ipctl->gpio_configs_lock, flags);
-
- list_for_each_entry_safe(gpio_pin, tmp, &ipctl->gpio_configs, list) {
- if (gpio_pin->pin_id == offset) {
- ret = s32_regmap_write(pctldev, gpio_pin->pin_id,
- gpio_pin->config);
- if (ret != 0)
- goto unlock;
-
- list_del(&gpio_pin->list);
- kfree(gpio_pin);
- break;
- }
- }
-
-unlock:
- spin_unlock_irqrestore(&ipctl->gpio_configs_lock, flags);
-}
-
static int s32_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned int offset,
@@ -449,8 +399,6 @@ static const struct pinmux_ops s32_pmx_ops = {
.get_function_name = s32_pmx_get_func_name,
.get_function_groups = s32_pmx_get_groups,
.set_mux = s32_pmx_set,
- .gpio_request_enable = s32_pmx_gpio_request_enable,
- .gpio_disable_free = s32_pmx_gpio_disable_free,
.gpio_set_direction = s32_pmx_gpio_set_direction,
};

@@ -669,6 +617,311 @@ static const struct pinconf_ops s32_pinconf_ops = {
.pin_config_group_dbg_show = s32_pinconf_group_dbg_show,
};

+static struct s32_pinctrl *to_s32_pinctrl(struct gpio_chip *chip)
+{
+ return container_of(chip, struct s32_pinctrl, gc);
+}
+
+static struct regmap *s32_gpio_get_pgpd_regmap(struct gpio_chip *chip,
+ unsigned int pin,
+ bool output)
+{
+ struct s32_pinctrl *ipctl = to_s32_pinctrl(chip);
+ struct nxp_siul2_mfd *mfd;
+ u32 base, num;
+ int i;
+
+ mfd = dev_get_drvdata(ipctl->dev->parent);
+
+ for (i = 0; i < mfd->num_siul2; i++) {
+ base = mfd->siul2[i].gpio_base;
+ num = mfd->siul2[i].gpio_num;
+
+ if (pin >= base && pin < base + num)
+ return output ? mfd->siul2[i].regmaps[SIUL2_PGPDO] :
+ mfd->siul2[i].regmaps[SIUL2_PGPDI];
+ }
+
+ return NULL;
+}
+
+static int s32_gpio_request(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct s32_pinctrl *ipctl = to_s32_pinctrl(gc);
+ struct pinctrl_dev *pctldev = ipctl->pctl;
+ struct gpio_pin_config *gpio_pin;
+ unsigned int config;
+ int ret;
+
+ ret = s32_regmap_read(pctldev, gpio, &config);
+ if (ret)
+ return ret;
+
+ /* Save current configuration */
+ gpio_pin = kmalloc(sizeof(*gpio_pin), GFP_KERNEL);
+ if (!gpio_pin)
+ return -ENOMEM;
+
+ gpio_pin->pin_id = gpio;
+ gpio_pin->config = config;
+
+ scoped_guard(spinlock_irqsave, &ipctl->gpio_configs_lock)
+ list_add(&gpio_pin->list, &ipctl->gpio_configs);
+
+ /* GPIO pin means SSS = 0 */
+ config &= ~S32_MSCR_SSS_MASK;
+
+ return s32_regmap_write(pctldev, gpio, config);
+}
+
+static void s32_gpio_free(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct s32_pinctrl *ipctl = to_s32_pinctrl(gc);
+ struct pinctrl_dev *pctldev = ipctl->pctl;
+ struct gpio_pin_config *gpio_pin, *tmp;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&ipctl->gpio_configs_lock, flags);
+
+ list_for_each_entry_safe(gpio_pin, tmp, &ipctl->gpio_configs, list) {
+ if (gpio_pin->pin_id == gpio) {
+ ret = s32_regmap_write(pctldev, gpio_pin->pin_id,
+ gpio_pin->config);
+ if (ret != 0)
+ goto unlock;
+
+ list_del(&gpio_pin->list);
+ kfree(gpio_pin);
+ break;
+ }
+ }
+
+unlock:
+ spin_unlock_irqrestore(&ipctl->gpio_configs_lock, flags);
+}
+
+static int s32_gpio_get_dir(struct gpio_chip *chip, unsigned int gpio)
+{
+ struct s32_pinctrl *ipctl = to_s32_pinctrl(chip);
+ unsigned int reg_value;
+ int ret;
+
+ ret = s32_regmap_read(ipctl->pctl, gpio, &reg_value);
+ if (ret)
+ return ret;
+
+ if (!(reg_value & S32_MSCR_IBE))
+ return -EINVAL;
+
+ return reg_value & S32_MSCR_OBE ? GPIO_LINE_DIRECTION_OUT :
+ GPIO_LINE_DIRECTION_IN;
+}
+
+static unsigned int s32_pin2pad(unsigned int pin)
+{
+ return pin / S32_PGPD_SIZE;
+}
+
+static u16 s32_pin2mask(unsigned int pin)
+{
+ /**
+ * From Reference manual :
+ * PGPDOx[PPDOy] = GPDO(x × 16) + (15 - y)[PDO_(x × 16) + (15 - y)]
+ */
+ return BIT(S32_PGPD_SIZE - 1 - pin % S32_PGPD_SIZE);
+}
+
+static struct regmap *s32_gpio_get_regmap_offset_mask(struct gpio_chip *chip,
+ unsigned int gpio,
+ unsigned int *reg_offset,
+ u16 *mask,
+ bool output)
+{
+ struct regmap *regmap;
+ unsigned int pad;
+
+ regmap = s32_gpio_get_pgpd_regmap(chip, gpio, output);
+ if (!regmap)
+ return NULL;
+
+ *mask = s32_pin2mask(gpio);
+ pad = s32_pin2pad(gpio);
+
+ *reg_offset = S32_PGPD(pad);
+
+ return regmap;
+}
+
+static void s32_gpio_set_val(struct gpio_chip *chip, unsigned int gpio,
+ int value)
+{
+ unsigned int reg_offset;
+ struct regmap *regmap;
+ u16 mask;
+
+ regmap = s32_gpio_get_regmap_offset_mask(chip, gpio, &reg_offset,
+ &mask, true);
+ if (!regmap)
+ return;
+
+ value = value ? mask : 0;
+
+ regmap_update_bits(regmap, reg_offset, mask, value);
+}
+
+static void s32_gpio_set(struct gpio_chip *chip, unsigned int gpio,
+ int value)
+{
+ if (s32_gpio_get_dir(chip, gpio) != GPIO_LINE_DIRECTION_OUT)
+ return;
+
+ s32_gpio_set_val(chip, gpio, value);
+}
+
+static int s32_gpio_get(struct gpio_chip *chip, unsigned int gpio)
+{
+ unsigned int reg_offset, value;
+ struct regmap *regmap;
+ u16 mask;
+ int ret;
+
+ if (s32_gpio_get_dir(chip, gpio) != GPIO_LINE_DIRECTION_IN)
+ return -EINVAL;
+
+ regmap = s32_gpio_get_regmap_offset_mask(chip, gpio, &reg_offset,
+ &mask, false);
+ if (!regmap)
+ return -EINVAL;
+
+ ret = regmap_read(regmap, reg_offset, &value);
+ if (ret)
+ return ret;
+
+ return !!(value & mask);
+}
+
+static int s32_gpio_dir_out(struct gpio_chip *chip, unsigned int gpio,
+ int val)
+{
+ struct s32_pinctrl *ipctl = to_s32_pinctrl(chip);
+
+ s32_gpio_set_val(chip, gpio, val);
+
+ return s32_pmx_gpio_set_direction(ipctl->pctl, NULL, gpio, false);
+}
+
+static int s32_gpio_dir_in(struct gpio_chip *chip, unsigned int gpio)
+{
+ struct s32_pinctrl *ipctl = to_s32_pinctrl(chip);
+
+ return s32_pmx_gpio_set_direction(ipctl->pctl, NULL, gpio, true);
+}
+
+static int s32_gpio_gen_names(struct device *dev, unsigned int cnt, char **names,
+ char *ch_index, unsigned int *num_index)
+{
+ unsigned int i;
+
+ for (i = 0; i < cnt; i++) {
+ if (i != 0 && !(*num_index % 16))
+ (*ch_index)++;
+
+ names[i] = devm_kasprintf(dev, GFP_KERNEL, "P%c_%02d",
+ *ch_index, 0xFU & (*num_index)++);
+ if (!names[i])
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int s32_gpio_remove_reserved_names(struct device *dev,
+ struct s32_pinctrl *ipctl,
+ char **names)
+{
+ struct device_node *np = dev->of_node;
+ u32 base_gpio, num_gpio, tmp;
+ int num_ranges, i, j, ret;
+
+ /* Parse the gpio-reserved-ranges to know which GPIOs to exclude. */
+
+ num_ranges = of_property_count_u32_elems(dev->of_node,
+ "gpio-reserved-ranges");
+
+ /* The "gpio-reserved-ranges" is optional. */
+ if (num_ranges < 0)
+ return 0;
+ num_ranges /= 2;
+
+ for (i = 0; i < num_ranges; i++) {
+ ret = of_property_read_u32_index(np, "gpio-reserved-ranges",
+ i * 2, &base_gpio);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Cannot parse start GPIO: %d\n",
+ ret);
+
+ ret = of_property_read_u32_index(np, "gpio-reserved-ranges",
+ i * 2 + 1, &num_gpio);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Cannot parse num. GPIOs: %d\n",
+ ret);
+
+ if (check_add_overflow(base_gpio, num_gpio, &tmp) ||
+ base_gpio + num_gpio > ipctl->gc.ngpio)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid reserved GPIOs\n");
+
+ /* Remove names set for reserved GPIOs. */
+ for (j = base_gpio; j < base_gpio + num_gpio; j++) {
+ devm_kfree(dev, names[j]);
+ names[j] = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int s32_gpio_populate_names(struct device *dev,
+ struct s32_pinctrl *ipctl)
+{
+ struct nxp_siul2_mfd *mfd = dev_get_drvdata(ipctl->dev->parent);
+ unsigned int num_index = 0;
+ char ch_index = 'A';
+ char **names;
+ int i, ret;
+
+ names = devm_kcalloc(dev, ipctl->gc.ngpio, sizeof(*names),
+ GFP_KERNEL);
+ if (!names)
+ return -ENOMEM;
+
+ for (i = 0; i < mfd->num_siul2; i++) {
+ if (mfd->siul2[i].gpio_base % 16 == 0)
+ num_index = 0;
+
+ ret = s32_gpio_gen_names(dev, mfd->siul2[i].gpio_num,
+ names + mfd->siul2[i].gpio_base,
+ &ch_index, &num_index);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Error setting SIUL2_%d names\n",
+ i);
+
+ ch_index++;
+ }
+
+ ret = s32_gpio_remove_reserved_names(dev, ipctl, names);
+ if (ret)
+ return ret;
+
+ ipctl->gc.names = (const char *const *)names;
+
+ return 0;
+}
+
#ifdef CONFIG_PM_SLEEP
static bool s32_pinctrl_should_save(struct s32_pinctrl *ipctl,
unsigned int pin)
@@ -896,12 +1149,14 @@ static int s32_pinctrl_probe_dt(struct platform_device *pdev,
int s32_pinctrl_probe(struct platform_device *pdev,
const struct s32_pinctrl_soc_data *soc_data)
{
+ struct nxp_siul2_mfd *mfd = dev_get_drvdata(pdev->dev.parent);
#ifdef CONFIG_PM_SLEEP
struct s32_pinctrl_context *saved_context;
#endif
struct pinctrl_desc *s32_pinctrl_desc;
struct s32_pinctrl_soc_info *info;
struct s32_pinctrl *ipctl;
+ struct gpio_chip *gc;
int ret;

if (!soc_data || !soc_data->pins || !soc_data->npins)
@@ -967,5 +1222,32 @@ int s32_pinctrl_probe(struct platform_device *pdev,

dev_info(&pdev->dev, "Initialized s32 pinctrl driver\n");

+ gc = &ipctl->gc;
+ gc->parent = &pdev->dev;
+ gc->label = dev_name(&pdev->dev);
+ gc->base = -1;
+ /* In some cases, there is a gap between the SIUL GPIOs. */
+ gc->ngpio = mfd->siul2[mfd->num_siul2 - 1].gpio_base +
+ mfd->siul2[mfd->num_siul2 - 1].gpio_num;
+ ret = s32_gpio_populate_names(&pdev->dev, ipctl);
+ if (ret)
+ return ret;
+
+ gc->set = s32_gpio_set;
+ gc->get = s32_gpio_get;
+ gc->set_config = gpiochip_generic_config;
+ gc->request = s32_gpio_request;
+ gc->free = s32_gpio_free;
+ gc->direction_output = s32_gpio_dir_out;
+ gc->direction_input = s32_gpio_dir_in;
+ gc->get_direction = s32_gpio_get_dir;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, gc, ipctl);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Unable to add gpiochip\n");
+
+ dev_info(&pdev->dev, "Initialized s32 GPIO functionality\n");
+
return 0;
}
--
2.45.2