0
votes

I am writing a kernel driver for a LCD. This LCD uses eight GPIO lines (d0...d7) to send data to display, some gpio control signals (on/off, enable backlight, and r/w) and a potentiometer to control the display contrast, connected to I2C bus.

I wrote a platform_driver that uses "probe" and "remove" callbacks to register/unregister a misc device that creates a /dev/lcd character device with can be used from userspace to send a buffer to be printed on the screen. I am able to read GPIOS properly defined on the DTS and also to manage those GPIOS to print strings on the LCD. This is the skeleton:

#define MODULE_NAME     "lcd"
static void lcd_hw_setup(void)
{ ...  }
static int lcd_open(struct inode *inode, struct file *file)
{ ... }
static ssize_t lcd_write (struct file *file, const char *buf, size_t     count, loff_t *ppos)
{ ... }
static int lcd_close(struct inode *inode, struct file *file)
{ ... }
/* declare & initialize file_operations structure */
static const struct file_operations lcd_dev_fops = {
    .owner = THIS_MODULE,
    .open = lcd_open,
    .write = lcd_write,
    .release = lcd_close
};
/* declare & initialize miscdevice structure */
static struct miscdevice lcd_misc = {
    .minor = MISC_DYNAMIC_MINOR, /* major = 10 assigned by the misc framework */
    .name = MODULE_NAME, /* /dev/lcd */
    .fops = &lcd_dev_fops,
};
static int lcd_probe(struct platform_device *pdev)
{
    struct device *dev;
    pr_info(MODULE_NAME ": lcd_probe init\n");
    /* Register the misc device with the kernel */
    misc_register(&lcd_misc);
    dev = &pdev->dev;
    /* gpiod_get calls to get gpios from DTS */
    lcd_hw_setup();
    pr_info(MODULE_NAME ": lcd_probe ok\n");
    return 0;
}
static int lcd_remove(struct platform_device *pdev)
{
    pr_info(MODULE_NAME ": lcd_remove\n");
    /* Release gpio resources */
    ... 
    /* Unregister the device with the kernel */
    misc_deregister(&lcd_misc);
    return 0;
}
/* declare a list of devices supported by this driver */
static const struct of_device_id lcd_of_ids[] = {
    { .compatible = "my-lcd" },
    { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, lcd_of_ids);
/* declare & initialize platform_driver structure */
static struct platform_driver lcd_pdrv = {
    .probe = lcd_probe,
    .remove = lcd_remove,
    .driver = {
        .name = "my-lcd", /* match with compatible */
        .of_match_table = lcd_of_ids,
        .owner = THIS_MODULE,
    },
};
/* register platform driver */
module_platform_driver(lcd_pdrv);

That works really fine.

Now I need to send to the I2C potentiometer a initialization value to set the display contrast. That requires calling i2c_smbus_write_byte_data. I need access to a i2c_client struct for that.

I found some I2C examples that create a i2c_driver which provides probe and remove callbacks, and receive a pointer to that i2c_client struct in the probe function. But I don't find a way to relate that i2c_driver with my platform_driver. They seem to be completely independent drivers.

My questions:

  • Can a platform_driver and a i2c_driver be combined in a single kernel module? I mean, can I add a module_platform_driver and module_i2c_driver calls in a single kernel module?

  • Or maybe I have to create a second driver just to control the I2C potentiometer. In this particular case there is a dependency between both kernel modules. How that dependency should be managed?

Please some help with this would be really helpful. Thanks a lot!

1
First of all, the driver most likely is already written (see drivers/auxdisplay folder). Second, in Linux that part with contrast is usually a different (or separate) driver. - 0andriy
I will check the driver pointed to see if it matches my needs. That makes sense. Your feedback is very useful to confirm I am in the right way. Thank you so much for your time! :) - criptobadia

1 Answers

0
votes

As an initial work around I have developed a second driver (an i2c_driver) just to call the i2c_smbus_write_byte_data function from the probe callback. It works, but I would like to know if this is the proper way to fix this issue. Thanks!