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, ®map_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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With