Re: [PATCH 1/2] gpio: realtek: Add GPIO support for RTD SoC variants

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Tzuyi!

thanks for your patch!

On Wed, Nov 1, 2023 at 3:58 AM Tzuyi Chang <tychang@xxxxxxxxxxx> wrote:

> This commit adds GPIO support for Realtek DHC RTD SoCs.

What does "DHC" mean? Please spell it out in the commit and Kconfig
so we know what it is.

> This driver enables configuration of GPIO direction, GPIO values, GPIO
> debounce settings and handles GPIO interrupts.
>
> Signed-off-by: Tzuyi Chang <tychang@xxxxxxxxxxx>
(...)
> +config GPIO_RTD
> +       tristate "Realtek DHC GPIO support"
> +       depends on ARCH_REALTEK
> +       default y
> +       select GPIOLIB_IRQCHIP
> +       help
> +         Say yes here to support GPIO on Realtek DHC SoCs.

Explain what DHC is i.e. the acronym expansion, family, use case or something.

> +#include <linux/bitops.h>
> +#include <linux/gpio.h>

Do not include this legacy header.
Include <linux/gpio/driver.h>

> +#include <linux/interrupt.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_irq.h>

I don't think you need any of thexe of_* includes.
Try it without them.

> +#include <linux/pinctrl/consumer.h>

Why?

> +/**
> + * struct rtd_gpio_info - Specific GPIO register information
> + * @name: GPIO device name
> + * @type: RTD GPIO ID
> + * @gpio_base: GPIO base number
> + * @num_gpios: Number of GPIOs
> + * @dir_offset: Offset for GPIO direction registers
> + * @dato_offset: Offset for GPIO data output registers
> + * @dati_offset: Offset for GPIO data input registers
> + * @ie_offset: Offset for GPIO interrupt enable registers
> + * @dp_offset: Offset for GPIO detection polarity registers
> + * @gpa_offset: Offset for GPIO assert interrupt status registers
> + * @gpda_offset: Offset for GPIO deassert interrupt status registers
> + * @deb_offset: Offset for GPIO debounce registers
> + */
> +struct rtd_gpio_info {
> +       const char *name;
> +       enum rtd_gpio_type type;
> +       unsigned int gpio_base;
> +       unsigned int num_gpios;
> +       unsigned int *dir_offset;
> +       unsigned int *dato_offset;
> +       unsigned int *dati_offset;
> +       unsigned int *ie_offset;
> +       unsigned int *dp_offset;
> +       unsigned int *gpa_offset;
> +       unsigned int *gpda_offset;
> +       unsigned int *deb_offset;

Use u8 instead of unsigned int for the offsets. It is clear from
the arrays you assign them that they are all u8[].

> +struct rtd_gpio {
> +       struct platform_device *pdev;
> +       const struct rtd_gpio_info *info;
> +       void __iomem *base;
> +       void __iomem *irq_base;
> +       struct gpio_chip gpio_chip;
> +       struct irq_chip irq_chip;

Do not use a dynamic irq_chip, create an immutable irq_chip
using a const struct.

See recent commits and virtually all current drivers in the tree
for examples on how to do that.

> +       int assert_irq;
> +       int deassert_irq;

I don't quite understand these two, but let's see in the rest
of the driver.

> +       .deb_offset = (unsigned int []){ 0x30, 0x34, 0x38, 0x3c, 0x40, 0x44, 0x48, 0x4c },
(...)
> +       .deb_offset = (unsigned int []){ 0x50 },

So clearly u8[]

> +static unsigned int rtd_gpio_deb_offset(struct rtd_gpio *data, unsigned int offset)
> +{
> +       return data->info->deb_offset[offset / 8];
> +}

So this is clearly counted by the GPIO number offset and the GPIO number
determines how far into the array we can index.

It looks a bit dangerous, it it possible to encode the array lengths better?

> +       if (data->info->type == RTD1295_ISO_GPIO) {
> +               shift = 0;
> +               deb_val += 1;
> +               write_en = BIT(shift + 3);
> +               reg_offset = rtd1295_gpio_deb_offset(data, offset);
> +       } else if (data->info->type == RTD1295_MISC_GPIO) {
> +               shift = (offset >> 4) * 4;
> +               deb_val += 1;
> +               write_en = BIT(shift + 3);
> +               reg_offset = rtd1295_gpio_deb_offset(data, offset);
> +       } else {
> +               shift = (offset % 8) * 4;
> +               write_en = BIT(shift + 3);
> +               reg_offset = rtd_gpio_deb_offset(data, offset);
> +       }

These three different offset functions seem a bit awkward.
Can we do this by just another index instead?

> +static int rtd_gpio_request(struct gpio_chip *chip, unsigned int offset)
> +{
> +       return pinctrl_gpio_request(chip->base + offset);
> +}
> +
> +static void rtd_gpio_free(struct gpio_chip *chip, unsigned int offset)
> +{
> +       pinctrl_gpio_free(chip->base + offset);
> +}

IIRC Bartosz has changed this for kernel v6.7, please check his upstream
commits and adjust the code accordingly.

> +static int rtd_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
> +{
> +       struct rtd_gpio *data = gpiochip_get_data(chip);
> +       u32 irq = 0;
> +
> +       irq = irq_find_mapping(data->domain, offset);
> +       if (!irq) {
> +               dev_err(&data->pdev->dev, "%s: can not find irq number for hwirq= %d\n",
> +                       __func__, offset);
> +               return -EINVAL;
> +       }
> +       return irq;
> +}

Don't implement your own gpio_to_irq, just use the GPIOLIB_IRQCHIP
helpers. See other drivers that select GPIOLIB_IRQCHIP, this
driver is nothing special.

> +       chained_irq_enter(chip, desc);
> +
> +       for (i = 0; i < data->info->num_gpios; i = i + 31) {
> +               gpa_reg_offset = rtd_gpio_gpa_offset(data, i);
> +               status = readl_relaxed(data->irq_base + gpa_reg_offset) >> 1;
> +               writel_relaxed(status << 1, data->irq_base + gpa_reg_offset);
> +
> +               while (status) {
> +                       j = __ffs(status);
> +                       status &= ~BIT(j);
> +                       hwirq = i + j;
> +                       if (rtd_gpio_check_ie(data, hwirq)) {
> +                               int irq = irq_find_mapping(data->domain, hwirq);
> +
> +                               generic_handle_irq(irq);
> +                       }

So you skip the interrupt handler if the interrupt is not enabled?

I think you should report spurious interrupts if they occur without
being enabled, unless there is some hardware flunky making these
lines flicker with noise interrupts too much.

> +static void rtd_gpio_deassert_irq_handle(struct irq_desc *desc)
> +{
> +       struct rtd_gpio *data = irq_desc_get_handler_data(desc);
> +       struct irq_chip *chip = irq_desc_get_chip(desc);
> +       unsigned int gpda_reg_offset;
> +       u32 status;
> +       int hwirq;
> +       int i;
> +       int j;
> +
> +       chained_irq_enter(chip, desc);
> +
> +       for (i = 0; i < data->info->num_gpios; i = i + 31) {
> +               gpda_reg_offset = rtd_gpio_gpda_offset(data, i);
> +               status = readl_relaxed(data->irq_base + gpda_reg_offset) >> 1;
> +               writel_relaxed(status << 1, data->irq_base + gpda_reg_offset);
> +
> +               while (status) {
> +                       j = __ffs(status);
> +                       status &= ~BIT(j);
> +                       hwirq = i + j;
> +                       if (rtd_gpio_check_ie(data, hwirq)) {
> +                               int irq = irq_find_mapping(data->domain, hwirq);
> +                               u32 irq_type = irq_get_trigger_type(irq);
> +
> +                               if ((irq_type & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH)
> +                                       generic_handle_irq(irq);
> +                       }
> +               }
> +       }
> +
> +       chained_irq_exit(chip, desc);
> +}

There is some code duplication here. Create wrapper calls with parameters
so you don't need to have several functions that look almost the same.

> +static int rtd_gpio_probe(struct platform_device *pdev)
> +{
> +       struct rtd_gpio *data;
> +       const struct of_device_id *match;
> +       struct device_node *node;

Don't go looking by the OF node, use the device:

struct device *dev = &pdev->dev;

> +       int ret;
> +       int i;
> +
> +       node = pdev->dev.of_node;

Use #include <linux/property.h>

> +       match = of_match_node(rtd_gpio_of_matches, pdev->dev.of_node);
> +       if (!match || !match->data)
> +               return -EINVAL;

Use
data->info = device_get_match_data(dev); instead
if (!data->info)...

> +
> +       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);

With a local dev you can just devm_kzalloc(dev, ...) etc.

> +       data->assert_irq = irq_of_parse_and_map(node, 0);
> +       if (!data->assert_irq)
> +               goto deferred;
> +
> +       data->deassert_irq = irq_of_parse_and_map(node, 1);
> +       if (!data->deassert_irq)
> +               goto deferred;

So one handler for rising and one handler for falling edge?
Hm that's different. I guess you need separate handlers.

> +       data->base = of_iomap(node, 0);
> +       if (!data->base)
> +               return -ENXIO;

Use
data->base = devm_platform_ioremap_resource(pdev, 0);

> +       data->irq_base = of_iomap(node, 1);
> +       if (!data->irq_base)
> +               return -ENXIO;

Use
data->irq_base = platform_get_irq(pdev, 1);

> +       data->gpio_chip.parent = &pdev->dev;

Don't assign this, the core will handle it.

> +       data->gpio_chip.label = dev_name(&pdev->dev);
> +       data->gpio_chip.of_gpio_n_cells = 2;

This is the default, let the core handle OF translation.

> +       data->gpio_chip.base = data->info->gpio_base;
> +       data->gpio_chip.ngpio = data->info->num_gpios;
> +       data->gpio_chip.request = rtd_gpio_request;
> +       data->gpio_chip.free = rtd_gpio_free;
> +       data->gpio_chip.get_direction = rtd_gpio_get_direction;
> +       data->gpio_chip.direction_input = rtd_gpio_direction_input;
> +       data->gpio_chip.direction_output = rtd_gpio_direction_output;
> +       data->gpio_chip.set = rtd_gpio_set;
> +       data->gpio_chip.get = rtd_gpio_get;
> +       data->gpio_chip.set_config = rtd_gpio_set_config;
> +       data->gpio_chip.to_irq = rtd_gpio_to_irq;

Use the GPIOLIB_IRQCHIP to provide this for you.

> +       data->irq_chip = rtd_gpio_irq_chip;

Convert to use immutable irq_chip. (Maybe several struct irq_chip if you need!)

> +       data->domain = irq_domain_add_linear(node, data->gpio_chip.ngpio,
> +                               &irq_domain_simple_ops, data);
> +       if (!data->domain) {
> +               devm_kfree(&pdev->dev, data);
> +               return -ENOMEM;
> +       }
> +
> +       for (i = 0; i < data->gpio_chip.ngpio; i++) {
> +               int irq = irq_create_mapping(data->domain, i);
> +
> +               irq_set_chip_data(irq, data);
> +               irq_set_chip_and_handler(irq, &data->irq_chip, handle_simple_irq);
> +       }
> +
> +       irq_set_chained_handler_and_data(data->assert_irq, rtd_gpio_assert_irq_handle, data);
> +       irq_set_chained_handler_and_data(data->deassert_irq, rtd_gpio_deassert_irq_handle, data);

Instead of doing this use GPIOLIB_IRQCHIP.

Before registering the gpio_chip set up stuff somewhat like this:

        girq = &data->gpio_chip.irq;
        gpio_irq_chip_set_chip(girq, &my_irq_chip);
        girq->parent_handler = my_gpio_irq_handler;
        girq->num_parents = 1;
        girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
                                     GFP_KERNEL);
        if (!girq->parents)
                ret = -ENOMEM;
        girq->default_type = IRQ_TYPE_NONE;
        girq->handler = handle_bad_irq;
        girq->parents[0] = irq;

But maybe in this case you want two parent IRQs? Not sure.

> +deferred:
> +       devm_kfree(&pdev->dev, data);
> +       return -EPROBE_DEFER;

Nope, when you return with an error from probe() all
allocations using devm_* are automatically free:ed that
is kind of the point of the managed resources.

Yours,
Linus Walleij





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux