Not able to set GPIO state in Linux Kernel Module

Hi, guys

I am writing a Linux Kernel Module for a TFT display, ILI9341. However, I am having problems even setting GPIO states.

It should turn on the GPIOs when I configure them, but it doesn’t.

Here is what I tried

  • Monitor for error messages in through dmesg → No error shown
  • Verify the GPIO configurations through duo-pinmux → All pins set to GPIO

Here is the code. I would love any suggestions

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>

enum ILI9341_ERROR {
    ILI9341_ERROR_NONE,
    ILI9341_ERROR_DC_PIN,
    ILI9341_ERROR_CS_PIN,
    ILI9341_ERROR_RST_PIN,
};


struct display {
    struct {
        int pin;
        struct gpio_desc *desc;
    } dc, cs, rst;
};
1

#define GPIOA_OFFSET 480

static struct display display = {
    .dc = {.pin=GPIOA_OFFSET + 23},
    .cs = {.pin=GPIOA_OFFSET + 24},
    .rst = {.pin=GPIOA_OFFSET + 25},
};


static inline
int ili9341_config_pins(void)
{
    int status = 0;
    display.dc.desc = gpio_to_desc(display.dc.pin);
    if (!display.dc.desc) {
        status = ILI9341_ERROR_DC_PIN;
        goto exit;
    }

    display.cs.desc = gpio_to_desc(display.cs.pin);
    if (!display.cs.desc) {
        status = ILI9341_ERROR_CS_PIN;
        goto error_cs;
    }

    display.rst.desc = gpio_to_desc(display.rst.pin);
    if (!display.rst.desc) {
        status = ILI9341_ERROR_RST_PIN;
        goto error_rst;
    }

error_rst:
    gpiod_put(display.cs.desc);
    display.cs.desc = NULL;
error_cs:
    gpiod_put(display.dc.desc);
    display.dc.desc = NULL;
exit:
    return status;
}


int ili9341_init(void)
{
    int status = 0;

    status = ili9341_config_pins();
    if (status) {
        goto exit;
    }

    gpiod_direction_output(display.dc.desc, 0);
    gpiod_direction_output(display.cs.desc, 0);
    gpiod_direction_output(display.rst.desc, 0);

// Here, they should turn on, but they don't
    gpiod_set_value(display.dc.desc, 1);
    gpiod_set_value(display.cs.desc, 1);
    gpiod_set_value(display.rst.desc, 1);
exit:
    return status;
}


void ili9341_deinit(void)
{
    gpiod_set_value(display.dc.desc, 0);
    gpiod_set_value(display.cs.desc, 0);
    gpiod_set_value(display.rst.desc, 0);

    gpiod_put(display.dc.desc);
    gpiod_put(display.cs.desc);
    gpiod_put(display.rst.desc);

    display.dc.desc = NULL;
    display.cs.desc = NULL;
    display.rst.desc = NULL;
}
MODULE_LICENSE("GPL");


int __init initialize(void) 
{
    int status = 0;

    pr_info("Initializing display\n");

    status = ili9341_init();
    if (status) {
        pr_err("Error initializing display: %d\n", status);
        goto exit;
    }

exit:
    return status;
}


void __exit terminate(void)
{
    pr_info("Deinitializing display\n");
    ili9341_deinit();
}


module_init(initialize);
module_exit(terminate);

Here is the makefile

PROJECT:=my_project
ARCH?=riscv
KDIR?=~/Documents/duo-buildroot-sdk/linux_5.10
CROSS_COMPILE?=~/.local/bin/gcc/riscv64-linux-x86_64/bin/riscv64-unknown-linux-gnu-
BUILD_DIR?=$(PROJECT_DIR)/build

PROJECT_DIR:=$(patsubst %/,%, $(dir $(realpath $(lastword $(MAKEFILE_LIST)))))
PWD:= $(shell pwd)

obj-m+=$(PROJECT).o
CODE_DIR:=$(PROJECT_DIR)/src
OUT_DIR:=$(PROJECT_DIR)/build
C_FILES:=$(patsubst $(PROJECT_DIR)/%,%,$(foreach d,$(CODE_DIR),$(shell find $(d) -type f -name '*.c')))
O_FILES:=$(patsubst %.c,%.o,$(C_FILES))

$(PROJECT)-objs+=$(O_FILES)

all:
	make -C $(KDIR) M=$(PROJECT_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) V=0 -I$(KDIR)/include modules 
	@mkdir -p $(OUT_DIR)
	@mv $(PROJECT_DIR)/*.ko $(PROJECT_DIR)/*.mod $(PROJECT_DIR)/*mod.c $(PROJECT_DIR)/*.o \
		$(PROJECT_DIR)/modules.order $(PROJECT_DIR)/Module.symvers $(PROJECT_DIR)/.*.cmd $(OUT_DIR)

clean:
	make -C $(KDIR) M=$(PROJECT_DIR) clean
	rm -rf $(OUT_DIR) 2>/dev/null || true


.PHONY: all clean insmod

Is there any other point I am missing?

I am using the MilkV Duo 64M.

Edit:

I didn’t even get to the part for configuring the SPI. I am still trying to set up the GPIOs for DC, RST, and CS.

I try to write to it using gpiod_set_value(…), but it doesn’t work (I have LEDs attached to the pins). The only pin that works is the XGPIOA_25 (the DC pin). The others don’t.

I’ve not got/used that particular TFT, but for ST7735 I changed the device tree and verified the pins were configured as SPI in the pinmux board init.

They are not that different.

Anyway, the board init is correct

I didn’t even get to the part for configuring the SPI.

I am trying yet to set up the GPIOs for DC, RST and CS.

I try to write to it with gpiod_set_value(…) but it doesn’t work (I have leds attached to the pins). The only pin that works is the XGPIOA_25 (the DC pin). The others don’t.

Edit: typos

If it helps, these are the pins I used.

PINMUX_CONFIG(SD0_PWR_EN, XGPIOA_14);    // Duo Pin 19 // BL
PINMUX_CONFIG(SPK_EN, XGPIOA_15);        // Duo Pin 20 // RES
PINMUX_CONFIG(SPINOR_MISO, XGPIOA_23);   // Duo Pin 21 // DC/AO
PINMUX_CONFIG(SD1_CLK, SPI2_SCK); // SCK
PINMUX_CONFIG(SD1_CMD, SPI2_SDO); // SDA
PINMUX_CONFIG(SD1_D3, SPI2_CS_X); // CS

I later changed IO23 to IO9 to free up SPINOR (but sacrificed I2C for that).