Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

i2c driver boot up - raspbian

I'm relative new to device drivers on linux. What im trying to achieve is that on boot-up of my Raspberry an external RGB driver will receive an i2c command so you can see a LED light up at boot.

My approach is trying to accomplish this via a kernel module that will be loaded at bootup. I tried a lot of things to achieve this, but at the moment I feel like I have a knowledge gap. Maybe someone can help me? (note that its not a hardware issue, from user space I can send commands to the device.)

My kernel module code is as following:

    #include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/regmap.h>


MODULE_AUTHOR ("Niels");
MODULE_DESCRIPTION("driver rgb led");
MODULE_LICENSE("GPL");

/*CAT3626 control registers*/
#define CAT3626_ADDRESS     0x66
#define CAT3626_ENA         0x03
#define CAT3626_REGA        0x00
#define CAT3626_REGB        0x01
#define CAT3626_REGC        0x02

struct cat3626 {
    struct device *dev;
    struct regmap * regmap;
};


enum {
    cat3626, 
};

static const struct of_device_id cat3626_dt_ids[] = {
    { .compatible = "onsemi,cat3626", .data = (void *)cat3626},
    { }
};

MODULE_DEVICE_TABLE(of, cat3626_dt_ids);


static const struct i2c_device_id cat3626_id[] = {
    {"cat3626",cat3626},
    { }
};

MODULE_DEVICE_TABLE(i2c, cat3626_id);

static const struct regmap_config regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
};

static int cat3626_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct cat3626 *cat3626;
    const struct of_device_id *match;
    int ret;

    cat3626 = devm_kzalloc(&client->dev, sizeof(struct cat3626), GFP_KERNEL);
    if (!cat3626){
        return -ENOMEM;
    }

    dev_set_drvdata(&client->dev, cat3626);
    cat3626->dev = &client->dev;

    cat3626->regmap = devm_regmap_init_i2c(client, &regmap_config);
    if (IS_ERR(cat3626->regmap)) {
        dev_err(cat3626->dev, "regmap allocation failed\n");
        return PTR_ERR(cat3626->regmap);
    }

    i2c_set_clientdata(client, cat3626);

    match = of_match_device(cat3626_dt_ids, &client->dev);
        if (!match) {
        dev_err(&client->dev, "unknown device model\n");
        return -ENODEV;
    }

    ret = i2c_smbus_write_byte_data(client, CAT3626_ENA, 0x30);   /* write LED C on*/
    ret = i2c_smbus_write_byte_data(client, CAT3626_REGC, 19);    /* write mA*/

    return ret;
}

static struct i2c_driver cat3626_driver = {
    .driver = {
        .name = "cat3626",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(cat3626_dt_ids),
    },
    .probe = cat3626_probe,
    .remove = cat3626_remove,
    .id_table = cat3626_id,
};

module_i2c_driver(cat3626_driver);

Here is the makefile:

ifneq ($(KERNELRELEASE),)
    obj-m := hiber_rgb_driver.o

else
    KERNELDIR ?= \
    /lib/modules/`uname -r`/build/
    PWD := `pwd`

default:
    $(MAKE) -C $(KERNELDIR) \
    M=$(PWD) modules

endif

clean:
    rm -f *.ko *.o Module* *mod*

In the /boot/config.txt file I have added this:

dtoverlay = i2c-gpio, bus = 80, i2c_gpio_delay_us = 2, i2c_gpio_sda = 44, i2c_gpio_scl = 45.

In addition I made a custom dtoverlay:

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = <&i2c80>;
        __overlay__ {
            status = "okay";
            #address-cells = <1>;
            #size-cells = <0>;

            cat3626: cat3626@66 {
                compatible = "onsemi,cat3626";
                reg = <0x66>;
                clock-frequency = <400000>;
            };
        };
    };
};

Unfortunalty on boot-up nothing happens. All I get from the bootup dmesg is the following:

rgb_driver: loading out-of-tree module taints kernel

Anyone can give me any help, or a maybe a different approach to achieve my goal?

Thanks in advance!

like image 331
Nelizzsan Avatar asked Feb 14 '20 16:02

Nelizzsan


1 Answers

A couple of things to look at - a tainted kernel is often feature-reduced and you probably don't want to go there if you don't have to. I'd try to solve the tainting issue. I've built kernel modules as standalones and not hit the taint issue. You may wish to revisit your makefile, this is a more standard module-building makefile with a couple of wrinkles as, of course, you are crossing compiling -

PWD = $(shell pwd)
obj-m += hiber_rgb_driver.o

all:
    make ARCH=arm CROSS_COMPILE=$(CROSS) -C $(KERNEL) SUBDIRS=$(PWD) modules

clean:
    make -C $(KERNEL) SUBDIRS=$(PWD) clean

and build it with something like -

make KERNEL=<LINUX_SOURCE_DIR> CROSS=<TOOLCHAIN_DIR>/bin/arm-linux-gnueabihf-

So there is that.

Next, your device probe stuff looks interesting. I don't have time to debug it for you, but I'd suggest adding some printk's in there to verify the probe is getting hit. If it is, then great, it's just a matter of figuring out why you are not 'matching'. If it is not getting hit, then read on ..

As you probably know, i2c buses are a little special when it comes to device probing. There is no real automated or magical probing that would normally happen on say a PCI bus. Instead you need to build out a device tree that the kernel can walk at boot-time to complete all of the probes.

I see that you've created an overlay snippet. You need to make sure that thing is compiled into a '.dtb' byte code binary that the kernel can parse and then put in the correct place in your boot media where grub can find it.

You may also need to update your device's master dtb to refer to this overlay, so that the kernel knows where it might go. Think of the device's dtb as being an artificial christmas tree, and the overlay as a limb that could be attached at some point in the future - you'll need to specify the attachment points in the device dtb. I wish that I could be more precise here, but hope sets you off in the correct direction on this point at least.

like image 80
Andrew Atrens Avatar answered Oct 16 '22 16:10

Andrew Atrens