--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2020 CS Group
+ * Charles Frey <charles.frey@c-s.fr>
+ *
+ * based on driver/gpio/mpc8xxx_gpio.c, which is
+ * Copyright 2016 Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc
+ *
+ * based on arch/powerpc/include/asm/mpc85xx_gpio.h, which is
+ * Copyright 2010 eXMeritus, A Boeing Company
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <dm.h>
+#include <mapmem.h>
+#include <asm/gpio.h>
+#include <malloc.h>
+
+enum {
+ MPC8XX_CPM1_PORTA,
+ MPC8XX_CPM1_PORTB,
+ MPC8XX_CPM1_PORTC,
+ MPC8XX_CPM1_PORTD,
+ MPC8XX_CPM1_PORTE,
+};
+
+/*
+ * The MPC885 CPU CPM has 5 I/O ports, and each ports has different
+ * register length : 16 bits for ports A,C,D and 32 bits for ports
+ * B and E.
+ *
+ * This structure allows us to select the accessors according to the
+ * port we are configuring.
+ */
+struct mpc8xx_gpio_data {
+ /* The bank's register base in memory */
+ void __iomem *base;
+ /* The address of the registers; used to identify the bank */
+ ulong addr;
+ /* The GPIO count of the bank */
+ uint gpio_count;
+ /* Type needed to use the correct accessors */
+ int type;
+};
+
+/* Structure for ports A, C, D */
+struct iop_16 {
+ u16 pdir;
+ u16 ppar;
+ u16 podr;
+ u16 pdat;
+};
+
+/* Port B */
+struct iop_32_b {
+ u32 pdir;
+ u32 ppar;
+ u32 podr;
+ u32 pdat;
+};
+
+/* Port E */
+struct iop_32_e {
+ u32 pdir;
+ u32 ppar;
+ u32 psor;
+ u32 podr;
+ u32 pdat;
+};
+
+union iop_32 {
+ struct iop_32_b b;
+ struct iop_32_e e;
+};
+
+inline u32 gpio_mask(uint gpio, int type)
+{
+ if (type == MPC8XX_CPM1_PORTB || type == MPC8XX_CPM1_PORTE)
+ return 1U << (31 - (gpio));
+ else
+ return 1U << (15 - (gpio));
+}
+
+static inline u16 gpio16_get_val(void __iomem *base, u16 mask, int type)
+{
+ struct iop_16 *regs = base;
+
+ return in_be16(®s->pdat) & mask;
+}
+
+static inline u16 gpio16_get_dir(void __iomem *base, u16 mask, int type)
+{
+ struct iop_16 *regs = base;
+
+ return in_be16(®s->pdir) & mask;
+}
+
+static inline void gpio16_set_in(void __iomem *base, u16 gpios, int type)
+{
+ struct iop_16 *regs = base;
+
+ clrbits_be16(®s->pdat, gpios);
+ /* GPDIR register 0 -> input */
+ clrbits_be16(®s->pdir, gpios);
+}
+
+static inline void gpio16_set_lo(void __iomem *base, u16 gpios, int type)
+{
+ struct iop_16 *regs = base;
+
+ clrbits_be16(®s->pdat, gpios);
+ /* GPDIR register 1 -> output */
+ setbits_be16(®s->pdir, gpios);
+}
+
+static inline void gpio16_set_hi(void __iomem *base, u16 gpios, int type)
+{
+ struct iop_16 *regs = base;
+
+ setbits_be16(®s->pdat, gpios);
+ /* GPDIR register 1 -> output */
+ setbits_be16(®s->pdir, gpios);
+}
+
+/* PORT B AND E */
+static inline u32 gpio32_get_val(void __iomem *base, u32 mask, int type)
+{
+ union iop_32 __iomem *regs = base;
+
+ if (type == MPC8XX_CPM1_PORTB)
+ return in_be32(®s->b.pdat) & mask;
+ else
+ return in_be32(®s->e.pdat) & mask;
+}
+
+static inline u32 gpio32_get_dir(void __iomem *base, u32 mask, int type)
+{
+ union iop_32 __iomem *regs = base;
+
+ if (type == MPC8XX_CPM1_PORTB)
+ return in_be32(®s->b.pdir) & mask;
+ else
+ return in_be32(®s->e.pdir) & mask;
+}
+
+static inline void gpio32_set_in(void __iomem *base, u32 gpios, int type)
+{
+ union iop_32 __iomem *regs = base;
+
+ if (type == MPC8XX_CPM1_PORTB) {
+ clrbits_be32(®s->b.pdat, gpios);
+ /* GPDIR register 0 -> input */
+ clrbits_be32(®s->b.pdir, gpios);
+ } else { /* Port E */
+ clrbits_be32(®s->e.pdat, gpios);
+ /* GPDIR register 0 -> input */
+ clrbits_be32(®s->e.pdir, gpios);
+ }
+}
+
+static inline void gpio32_set_lo(void __iomem *base, u32 gpios, int type)
+{
+ union iop_32 __iomem *regs = base;
+
+ if (type == MPC8XX_CPM1_PORTB) {
+ clrbits_be32(®s->b.pdat, gpios);
+ /* GPDIR register 1 -> output */
+ setbits_be32(®s->b.pdir, gpios);
+ } else {
+ clrbits_be32(®s->e.pdat, gpios);
+ /* GPDIR register 1 -> output */
+ setbits_be32(®s->e.pdir, gpios);
+ }
+}
+
+static inline void gpio32_set_hi(void __iomem *base, u32 gpios, int type)
+{
+ union iop_32 __iomem *regs = base;
+
+ if (type == MPC8XX_CPM1_PORTB) {
+ setbits_be32(®s->b.pdat, gpios);
+ /* GPDIR register 1 -> output */
+ setbits_be32(®s->b.pdir, gpios);
+ } else {
+ setbits_be32(®s->e.pdat, gpios);
+ /* GPDIR register 1 -> output */
+ setbits_be32(®s->e.pdir, gpios);
+ }
+}
+
+static int mpc8xx_gpio_direction_input(struct udevice *dev, uint gpio)
+{
+ struct mpc8xx_gpio_data *data = dev_get_priv(dev);
+ int type = data->type;
+
+ if (type == MPC8XX_CPM1_PORTB || type == MPC8XX_CPM1_PORTE)
+ gpio32_set_in(data->base, gpio_mask(gpio, type), type);
+ else
+ gpio16_set_in(data->base, gpio_mask(gpio, type), type);
+
+ return 0;
+}
+
+static int mpc8xx_gpio_set_value(struct udevice *dev, uint gpio, int value)
+{
+ struct mpc8xx_gpio_data *data = dev_get_priv(dev);
+ int type = data->type;
+
+ if (type == MPC8XX_CPM1_PORTB || type == MPC8XX_CPM1_PORTE) {
+ if (value)
+ gpio32_set_hi(data->base, gpio_mask(gpio, type), type);
+ else
+ gpio32_set_lo(data->base, gpio_mask(gpio, type), type);
+ } else {
+ if (value)
+ gpio16_set_hi(data->base, gpio_mask(gpio, type), type);
+ else
+ gpio16_set_lo(data->base, gpio_mask(gpio, type), type);
+ }
+
+ return 0;
+}
+
+static int mpc8xx_gpio_direction_output(struct udevice *dev, uint gpio,
+ int value)
+{
+ return mpc8xx_gpio_set_value(dev, gpio, value);
+}
+
+static int mpc8xx_gpio_get_value(struct udevice *dev, uint gpio)
+{
+ struct mpc8xx_gpio_data *data = dev_get_priv(dev);
+ int type = data->type;
+
+ /* Input -> read value from GPDAT register */
+ if (type == MPC8XX_CPM1_PORTB || type == MPC8XX_CPM1_PORTE)
+ return gpio32_get_val(data->base, gpio_mask(gpio, type), type);
+ else
+ return gpio16_get_val(data->base, gpio_mask(gpio, type), type);
+}
+
+static int mpc8xx_gpio_get_function(struct udevice *dev, uint gpio)
+{
+ struct mpc8xx_gpio_data *data = dev_get_priv(dev);
+ int type = data->type;
+ int dir;
+
+ if (type == MPC8XX_CPM1_PORTB || type == MPC8XX_CPM1_PORTE)
+ dir = gpio32_get_dir(data->base, gpio_mask(gpio, type), type);
+ else
+ dir = gpio16_get_dir(data->base, gpio_mask(gpio, type), type);
+ return dir ? GPIOF_OUTPUT : GPIOF_INPUT;
+}
+
+static int mpc8xx_gpio_ofdata_to_platdata(struct udevice *dev)
+{
+ struct mpc8xx_gpio_plat *plat = dev_get_plat(dev);
+ fdt_addr_t addr;
+ u32 reg[2];
+
+ dev_read_u32_array(dev, "reg", reg, 2);
+ addr = dev_translate_address(dev, reg);
+
+ plat->addr = addr;
+ plat->size = reg[1];
+ plat->ngpios = dev_read_u32_default(dev, "ngpios", 32);
+
+ return 0;
+}
+
+static int mpc8xx_gpio_platdata_to_priv(struct udevice *dev)
+{
+ struct mpc8xx_gpio_data *priv = dev_get_priv(dev);
+ struct mpc8xx_gpio_plat *plat = dev_get_plat(dev);
+ unsigned long size = plat->size;
+ int type;
+
+ if (size == 0)
+ size = 0x100;
+
+ priv->addr = plat->addr;
+ priv->base = map_sysmem(plat->addr, size);
+
+ if (!priv->base)
+ return -ENOMEM;
+
+ priv->gpio_count = plat->ngpios;
+
+ type = dev_get_driver_data(dev);
+
+ if ((type == MPC8XX_CPM1_PORTA || type == MPC8XX_CPM1_PORTC ||
+ type == MPC8XX_CPM1_PORTD) && plat->ngpios == 32)
+ priv->gpio_count = 16;
+
+ priv->type = type;
+
+ return 0;
+}
+
+static int mpc8xx_gpio_probe(struct udevice *dev)
+{
+ struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct mpc8xx_gpio_data *data = dev_get_priv(dev);
+ char name[32], *str;
+
+ mpc8xx_gpio_platdata_to_priv(dev);
+
+ snprintf(name, sizeof(name), "MPC@%lx_", data->addr);
+ str = strdup(name);
+
+ if (!str)
+ return -ENOMEM;
+
+ uc_priv->bank_name = str;
+ uc_priv->gpio_count = data->gpio_count;
+
+ return 0;
+}
+
+static const struct dm_gpio_ops gpio_mpc8xx_ops = {
+ .direction_input = mpc8xx_gpio_direction_input,
+ .direction_output = mpc8xx_gpio_direction_output,
+ .get_value = mpc8xx_gpio_get_value,
+ .set_value = mpc8xx_gpio_set_value,
+ .get_function = mpc8xx_gpio_get_function,
+};
+
+static const struct udevice_id mpc8xx_gpio_ids[] = {
+ { .compatible = "fsl,cpm1-pario-bank-a", .data = MPC8XX_CPM1_PORTA },
+ { .compatible = "fsl,cpm1-pario-bank-b", .data = MPC8XX_CPM1_PORTB },
+ { .compatible = "fsl,cpm1-pario-bank-c", .data = MPC8XX_CPM1_PORTC },
+ { .compatible = "fsl,cpm1-pario-bank-d", .data = MPC8XX_CPM1_PORTD },
+ { .compatible = "fsl,cpm1-pario-bank-e", .data = MPC8XX_CPM1_PORTE },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(gpio_mpc8xx) = {
+ .name = "gpio_mpc8xx",
+ .id = UCLASS_GPIO,
+ .ops = &gpio_mpc8xx_ops,
+ .of_to_plat = mpc8xx_gpio_ofdata_to_platdata,
+ .plat_auto = sizeof(struct mpc8xx_gpio_plat),
+ .of_match = mpc8xx_gpio_ids,
+ .probe = mpc8xx_gpio_probe,
+ .priv_auto = sizeof(struct mpc8xx_gpio_data),
+};