i2c driver boot up - raspbian - linux

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!

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.

Related

Error inserting kernel module on target device

I have written a basic kernel module which creates a thread and then starts the thread.
I am compiling the kernel module for Beagle bone black board and i have the kernel source code for Beagle bone black.
The kernel module source code(kmodule.c) and Makefile is given below.
/**kmodule.c**/
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/kthread.h>
#include<linux/types.h>
#include<linux/sched.h> //For wake_up_process()
static struct task_struct *th_handler = NULL;
static s32 thread_function(void *pdata);
static s32 __init thread_module_init(void)
{
th_handler = kthread_create(thread_function,NULL,"test_thread");
if(!IS_ERR_OR_NULL(th_handler))
{
wake_up_process(th_handler);
}
pr_info("%s done.",__FUNCTION__);
return 0;
}
static void __exit thread_module_deinit(void)
{
pr_info("%s done.",__FUNCTION__);
}
static s32 thread_function(void *pdata)
{
pr_info("Inside thread function.");
}
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1");
MODULE_AUTHOR("Author name");
MODULE_DESCRIPTION("Description");
/**Makefile**/
export ARCH=arm
export CROSS_COMPILE=~/cross_compiler/arm-linux-gnueabi- #Cross compiler for Beagle Bone black
BBB_KERNEL_SRC_PATH=~/bbb_kernelsrc #Kernel source path for Beagle bone black.
obj-m += threadmodule.o
threadmodule-objs := kmodule.o
all:
make -C $(BBB_KERNEL_SRC_PATH) M=$(PWD) modules
clean:
make -C $(BBB_KERNEL_SRC_PATH) M=$(PWD) clean
The kernel module builds fine without errors and .ko file is generated.
When i insert the kernel module on Beagle bone board black i get the following error.
insmod: ERROR: could not insert module threadmodule.ko: Invalid
parameters
Also dmesg|tail -20 output:
threadmodule: disagrees about version version of symbol wake_up_process
threadmodule: Unknown symbol wake_up_process(err -22)
Any idea why this error is coming.
Beagle bone black kernel source version : 4.19.94

Simple GPIO Device Tree Example for Beaglebone Black Deb 10.3

My goal is to write a simple .dts file (to be compiled to .dtbo using DT 1.4.4) to configure a GPIO output on boot on a Beaglebone Black Rev C running Debian 10.3
I intend to place the .dtbo in /lib/firmware and then specify it in /boot/uEnv.txt
I understand some parts of the .dts file and have tried decompiling exisiting .dtbo files in /lib/firmware/ for guidance but none of them are a simple GPIO output example. A lot of online resources involve make and make install but I believe DT should be able to handle it by now right?
I was able to get the following to compile but with issue:
/* dtc -O dtb -o BB-P8_13-LED.dtbo -b 0 -# BB-P8_13-LED-00A0.dts */
/dts-v1/;
/plugin/;
/ {
compatible = "ti,beaglebone-black";
/* identification */
part-number = "BB-P8_13-LED";
version = "00A0";
/* state the resources this cape uses */
exclusive-use =
/* the pin header uses */
"P8.13", /* GPIO_23 */
/* the hardware ip uses */
"gpio23";
fragment#0 {
target = <&am33xx_pinmux>;
__overlay__ {
bb_gpio23_pin: pinmux_bb_gpio23_pin {
pinctrl-single,pins = < 0x024 0x07 >; /*P8_13 GPIO23 MODE7*/
};
};
};
fragment#1 {
target = <&gpio23>;
__overlay__ {
leds {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&bb_gpio23_pin>;
compatible = "gpio-leds";
P8_13 {
label = "P8_13";
default-state = "on";
};
};
};
};
};
Q: Why does loading this .dtbo in /boot/uEnv.txt cause all other GPIOs to disappear from /sys/class/gpio/? I thought fragment0 was for excluding a single gpio, not all of them.
###Additional custom capes
uboot_overlay_addr4=/lib/firmware/BB-P8_13-LED-00A0.dtbo
Q: Where are the files for controlling the GPIO (for testing) or rather what can I add to my .dts file so the gpio23 still appears in /sys/class/gpio or even /sys/class/leds? Ultimately I want to be able to control this GPIO with Node-RED.
Q: Do I need to be consistent with my use of P8.13 vs. P8_13? I think I'm mixing up terminology used in .dts files that get compiled with make vs DT.
Q: I think my fragment#1 P8_13 child node is missing something to specify the gpio bank and active high/low setting. Something like "gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>;" Where can I look to research which bank GPIO23 is in? What does the '19' mean in that statement?
So Dr. Derek Molly did a really nice job of laying this out and I was able to use the example in his repo. Here is a page he made for explaining how to configure GPIO at boot using Device Tree Overlays:
http://derekmolloy.ie/beaglebone/beaglebone-gpio-programming-on-arm-embedded-linux/
Even though his solution is for kernel 3.8 I was able to get the following to compile on 4.19
/* dtc -O dtb -o BB-P8_13-LED-00A0.dtbo -b 0 -# BB-P8_13-LED-00A0.dts */
/dts-v1/;
/plugin/;
/{
compatible = "ti,beaglebone-black";
part-number = "BB-P8_13-LED";
version = "00A0";
fragment#0 {
target = <&am33xx_pinmux>;
__overlay__ {
pinctrl_test: BB-P8_13-LED {
pinctrl-single,pins = <
0x024 0x27 /* P8_13 9 PULLUP ENABLED OUTPUT MODE7 - The LED Output */
>;
};
};
};
fragment#1 {
target = <&ocp>;
__overlay__ {
test_helper: helper {
compatible = "bone-pinmux-helper";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
status = "okay";
};
};
};
};
All that needs to be edited for a different GPIO pin is the "0x024" (address offset) and the "0x27" to set various aspects of the GPIO like pullup vs. pulldown and pinmux mode. Derek Molly has an older version of his guide which has the table for building the pinmux binary values (that need to be converted to hex): http://derekmolloy.ie/gpios-on-the-beaglebone-black-using-device-tree-overlays/
Most of the information is available in the Beaglebone SRM which I should probably read at some point.
The .dts file in my first answer did not fix my problem. GPIO P8_13 still boots as an input. After more digging and testing I have discovered it is NOT possible to make a GPIO direction survive a reboot. It will always boot to the default and the best you can do is enable a pullup or pulldown resistor to keep the pin high or low until a custom service file (or program) can write to /sys/class/gpio/gpioXXX/direction. I even tried decompiling am335x-boneblack.dtb, editing it, and re-compiling with no luck.
This is sad and incredibly frustrating. What good is an output that flickers during reboot? Guess I'll have to compensate with fancy external circuitry.
Per the original question, you can author a dts file where the GPIO remains in /sys/gpio/class/. See the article I wrote below for an example that works this way.
https://takeofftechnical.com/beaglebone-black-led-control/

Device interfacing using SMBus on Beaglebone Black

I am using ADM1032ARMZ-2R temperature sensor (as a beginner I am not allowed to post more than 2 links here, I have already posted 2 links for the images below - For datasheet please google ADM1032ARMZ-2R) with Beaglebone Black Rev C having pre-installed Debian (kernel - 3.8.13) . The datasheet of ADM1032 says that chip is available for communication at address 0x4D via SMBus Protocol. I have never worked on SMbus before but found that I2C is compatible with SMBus for 100 KHz and less. I tried doing following things
I wrote a device tree overlay. It looks like following (I have kept the frequency as 100 KHz)
// Device Tree Overlay for I2C1 uses Pins P9.17(SCLK) & P9.18(SDA)
/dts-v1/;
/plugin/;
/ {
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "BBB-I2C1-4D";
version = "00A0";
exclusive-use =
"P9.18", /* i2c1_sda */
"P9.17", /* i2c1_scl */
/* the hardware ip uses */
fragment#0 {
target = <&am33xx_pinmux>;
__overlay__ {
bb_i2c1_pins: pinmux_bb_i2c1_pins {
pinctrl-single,pins = <
0x158 0x72 /* spi0_d1.i2c1_sda, SLEWCTRL_SLOW | INPUT_PULLUP | MODE2 */
0x15c 0x72 /* spi0_cs0.i2c1_scl, SLEWCTRL_SLOW | INPUT_PULLUP | MODE2 */
>;
};
};
};
fragment#1 {
target = <&i2c1>;
__overlay__ {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&bb_i2c1_pins>;
clock-frequency = <100000>;
#address-cells = <1>;
#size-cells = <0>;
/* adding adm1032 as i2c child device on the bus here */
tempsense: tempsense#4d {
compatible = "onsemi,adm1032";
reg = <0x4d>;
};
};
};
};
Compiled this file, copied the resultant .dtbo file in the /lib/firmware directory and successfully inserted it as a virtual cape in the /sys/devices/bone_capemgr.9/slots using echo command. It looks like this
I tried to observe the correctness of the above work using the following command
dtc -f -I fs /proc/device-tree | less
Intended things are happening and it looks like this (Please note that, for i2c1, the "ti,hwmods" field in the image shows "i2c2", but its actually i2c1 so please don't worry about it. I have read about it made sure that whatever it's showing is correct)
Now I am trying to do
i2cdetect -r 1
But I am not able to detect anything
at 0x02, there is a Status register available for reading
i2cget -y -f 0x4d 0x02
I get Read failed response
Where am I going wrong ? Please help.

Using persistent storage in linux kernel

I am trying to use persistent store(Pstore) available in linux kernel but somehow I am not getting the logs in case of kernel panics. I made the following kernel modules in kernel config file as built in:
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_RAM=y
Now a/c to documentation pstore I should get the logs on next reboot in /sys/fs/pstore/... (or /dev/pstore/...) but couldn't find the logs there.
Am I missing something...?
Besides the kernel config options:
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_PSTORE_RAM=y
I had to reserve a part of memory in which the logs will be saved. I did this through the device tree like this:
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
pstore: pstore#FF00000 {
no-map;
reg = <0x0 0xFF00000 0x0 0x00100000>; /* pstore/ramoops buffer
starts at memory address 0xFF00000 and is of size 0x00100000 */
};
};
ramoops {
compatible = "ramoops";
memory-region = <&pstore>;
record-size = <0x0 0x00020000>;
console-size = <0x0 0x00020000>;
pmsg-size = <0x0 0x00020000>;
};
Also, check if the following kernel patch is present (which is needed for the kernel to be able to parse the options given above):
https://android-review.googlesource.com/#/c/kernel/common/+/195160/
Another way is to turn it on through the kernel cmdline, for example:
ramoops.mem_address=0x30000000 ramoops.mem_size=0x100000 memmap=0x100000$0x30000000
check if below config options are enabled
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
**CONFIG_PSTORE_FTRACE=y**
CONFIG_PSTORE_RAM=y
But why /dev/pstore file?, it's not needed,
To check if console-ramoops working,
do echo Trigger a kernel panic using command
echo c > /proc/sysrq-trigger
then reboot device manually. after system boots up, run command "/sys/fs/pstore/console-ramoops", check if console_ramoops has got anything logged.
You have to declare platform data as described by #shadowfire. But all kernel cannot use device tree for ramoops.
Generic way to do it, is to add in the source file for your hardware (example for mx6ul : arch/arm/mach-imx/mach-imx6ul.c) the following piece of code in the function xxx_init_machine :
#include <linux/pstore_ram.h>
[...]
static struct ramoops_platform_data ramoops_data = {
.mem_size = <...>,
.mem_address = <...>,
.mem_type = <...>,
.record_size = <...>,
.dump_oops = <...>,
.ecc = <...>,
};
static struct platform_device ramoops_dev = {
.name = "ramoops",
.dev = {
.platform_data = &ramoops_data,
},
};
[... inside xxx_init_machine ...]
int ret;
ret = platform_device_register(&ramoops_dev);
if (ret) {
printk(KERN_ERR "unable to register platform device\n");
return ret;
}
More info available in documentation : ramoops
Do you have logs in dmesg which can confirm that ramoops is enabled successfully ?
# dmesg | grep -i "pstore\|ramoops"
console [pstore-1] enabled
pstore: Registered ramoops as persistent store backend
ramoops: attached 0x20000#0x80000000, ecc: 0/0
Moreover you must mount pstore directory, that's why the directory is empty on your rootfs :
mount -t pstore -o kmsg_bytes=8000 - /sys/fs/pstore
Default size for the mount is 10 Kbytes in you don't set the kmsg_bytes option. You can use /etc/fstab to mount it automatically.

Howto Write to the GPIO Pin of the CM108 Chip in Linux?

The CM108 from C-Media has 4 GPIO pin that you can access via a hid interface.
Using the generic write function in Windows I was able to write to the gpio pins.
However I'm trying to do the same thing in Linux without success.
The linux kernel detect the device as a hidraw device.
Note: I was able to read from the device, just not write. (I've run the app as root just to make sure it wasn't a permission issue).
I got this working, here's how.
I needed to create a new linux hid kernel mod. (it wasn't that hard)/*
/*
* Driver for the C-Media 108 chips
*
* Copyright (C) 2009 Steve Beaulac <steve#sagacity.ca>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2.
*/
/*
* This driver is based on the cm109.c driver
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#define DRIVER_VERSION "20090526"
#define DRIVER_AUTHOR "Steve Beaulac"
#define DRIVER_DESC "C-Media 108 chip"
#define CM108_VENDOR_ID 0x0d8c
#define CM108_PRODUCT_ID 0x000c
#ifdef CONFIG_USB_DYNAMIC_MINORS
#define CM108_MINOR_BASE 0
#else
#define CM108_MINOR_BASE 96
#endif
/*
* Linux interface and usb initialisation
*/
static int cm108_hid_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
dev_err(&hdev->dev, "parse failed\n");
goto error;
}
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret) {
dev_err(&hdev->dev, "hw start failed\n");
goto error;
}
return 0;
error:
return ret;
}
static struct hid_device_id cm108_device_table[] = {
{ HID_USB_DEVICE (CM108_VENDOR_ID, CM108_PRODUCT_ID) },
/* you can add more devices here with product ID 0x0008 - 0x000f */
{ }
};
MODULE_DEVICE_TABLE (hid, cm108_device_table);
static struct hid_driver hid_cm108_driver = {
.name = "cm108",
.id_table = cm108_device_table,
.probe = cm108_hid_probe,
};
static int hid_cm108_init(void)
{
return hid_register_driver(&hid_cm108_driver);
}
static void hid_cm108_exit(void)
{
hid_unregister_driver(&hid_cm108_driver);
}
module_init(hid_cm108_init);
module_exit(hid_cm108_exit);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
used This makefile
obj-m += cm108.o
and compile the module
make -C /lib/modules/`uname -r`/build/ M=`pwd` EXTRAVERSION="-generic" modules
sudo make -C /lib/modules/`uname -r`/build/ M=`pwd` EXTRAVERSION="-generic" modules_install
depmod -a
I had to modify the modules.order file so that my module would get queried before the generic hid linux module.
This modules make sure that the hidraw uses Interface 2.
Then I can use fopen to read and write to the GPIO pin of the CM108 chip.
BTW: when writing you need to write 5byte the 1st byte is used for the HID_OUTPUT_REPORT
Most hardware in Linux is accessible as a file. If the driver created a hardware node for it on the file-system, you're in luck. You will be able to write to it using regular file routines. Otherwise, you may need to do some assembly magic, which may require you to write a kernel module to do it.
Here is a complete example of how to write to the CM108/CM119 GPIO pins on Linux.
https://github.com/wb2osz/direwolf/blob/dev/cm108.c
You don't need to run as root or write your own device driver.
I have the opposite problem. I'm trying to figure out how to do the same thing on Windows.

Resources