I'm a beginnig learner of linux driver, so far I've studied how to write an basic char device driver and platform driver. I'm pricticing on the led example, I want to improve it from basic char device driver model to platform driver model.
In the earlier practice, I define a global int gpio_num[MAX_LED_NUM] array to keep the led's gpio. It's easy because I can indentify the led by device's minor number, and operate the corresponding led by referencing gpio_num[minor];
But in this improvement, I don't want to use global array to keep led's gpio because I can't predict how many leds on the board. So I malloc a structure for every platform device in the probe function to keep their own gpio, and call device_create() to create device node. It seems good so far because how many leds are there, there are how many structures and device nodes in the /dev directory, and there is no global variables.
In order to seperate led operation functions from char device driver, I define the led operaions functions in the platform driver part(driver.c) and pass the function sets to the char device driver part(leds.c). for example , I define the alpha_led_init(struct drv_priv *priv) in the driver.c, and call it from char device's open function(in leds.c) .
in order to call alpha_led_init(struct drv_priv *priv), the open function needs the parameter *priv(the private data of platform device which contains led_gpio). I've pass the private data to the char device by using device_create()'s third parameter. But how can I get it from the open function ? I can't get the struct device *pdev in the open function, so I can't call dev_get_drvdata(pdev) to get the platform device's private data , so there's no way to call alpha_led_init(struct drv_priv *priv).
Is my program model very bad? Any good way to pass platform device's private data to char device ? Any help or advice would be appreciating.
Below is my practicing code, for simplicity, some header files're omitted.
alpha_led.h
#ifndef __ALPHA_LED_H__
#define __ALPHA_LED_H__
#define LED_OFF     (1)
#define LED_ON      (0)
#define LED_MAX_NUM (10)
struct drv_priv
{
    int led_gpio;
};
struct alpha_led_operations
{
    int inited;
    int (*alpha_led_init)(struct drv_priv *pdev);
};
#endif
driver.c
#include "alpha_led.h"
static int led_count;
static int alpha_led_init(struct drv_priv *priv)
{
    int err;
    char name[64];
    if(!priv)
        return -1;
    memset(name, 0, sizeof(name));
    snprintf(name, sizeof(name), "alpha-led-pin-%d", priv->led_gpio);
    err = gpio_request(priv->led_gpio, name);
    if(err)
        return -1;
    err = gpio_direction_output(priv->led_gpio, LED_OFF);
    if(err) {
        gpio_free(priv->led_gpio);
        return -1;
    }
    return 0;
}
static int alpha_led_probe(struct platform_device *pdev)
{
    int err, gpio;
    const char *status = NULL;
    struct drv_priv *priv = NULL;
    struct device_node *np = pdev->dev.of_node;
    if(!np)
        return -1;
    err = of_property_read_string(np, "status", &status);
    if(err || (strcmp(status, "okay") != 0))
        return -1;
    gpio = of_get_named_gpio(np, "led-gpio", 0);
    if(gpio < 0)
        return -1;
    // I malloc a drv_priv structure for every platform device to keep their private data
    priv = devm_kzalloc(&pdev->dev, sizeof(struct drv_priv), GFP_KERNEL);
    if(!priv)
        return -ENOMEM;
    platform_set_drvdata(pdev, priv);
    // for every platform device, the gpio number is their private data.
    priv->led_gpio = gpio;
    // I call self-defined function in leds.c to create device node in /dev directory
    // and pass the platform device's private data(priv) to the device_create()
    return create_led_device_node(led_count++, np->name, priv);
}
static int alpha_led_remove(struct platform_device *pdev)
{
    // get the platform device's private data
    struct drv_priv *priv = platform_get_drvdata(pdev);
    gpio_free(priv->led_gpio);
}
static const struct of_device_id alpha_led_of_match[] = {
    { .compatible = "alientek-alpha,led" },
    {}
};
static struct platform_driver alpha_led_driver = {
    .probe = alpha_led_probe,
    .remove = alpha_led_remove,
    .driver = {
        .name = "alpha-led",
        .of_match_table = alpha_led_of_match,
    }
};
static int __init platform_driver_led_init(void)
{
    int rc;
    struct alpha_led_operations *ops;
    rc = platform_driver_register(&alpha_led_driver);
    // pass the lower led control functions to leds.c
    ops = get_alpha_led_ops();
    ops->alpha_led_init = alpha_led_init;
    ops->inited = 1;
    return 0;
}
static void __exit platform_driver_led_exit(void)
{
    platform_driver_unregister(&alpha_led_driver);
}
module_init(platform_driver_led_init);
module_exit(platform_driver_led_exit);
MODULE_AUTHOR("David");
MODULE_LICENSE("GPL");
leds.c
#include "alpha_led.h"
#define LED_DEV_NAME ("alpha-led")
#define LED_CLASS_NAME ("alpha-led-class")
static int led_major;
static struct cdev led_cdev;
static struct class *led_class;
static int led_open(struct inode *inode, struct file *filp);
static int led_close(struct inode *inode, struct file *filp);
static struct alpha_led_operations alpha_led_ops;
static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close,
};
static int led_open(struct inode *inode, struct file *filp)
{
    int err, minor;
    if(!inode || !filp)
        return -1;
    if(!alpha_led_ops.inited)
        return -1;
    if(!alpha_led_ops.alpha_led_init)
        return -1;
    //Question: here I want to call alpha_led_init(struct drv_priv *priv) defined in the driver.c
    //But how can I get the parameter priv ? I know I have set it in the device_create(), but how can I fetch it here?
    //Or Am writing a very bad platform driver model?
    return alpha_led_ops.alpha_led_init(...);
}
static int led_close(struct inode *inode, struct file *filp)
{
    return 0;
}
static int __init chrdev_led_init(void)
{
    dev_t devid;
    int i, rc, major, minor;
    struct device *pdev;
    if (led_major) {
        devid = MKDEV(led_major, 0);
        rc = register_chrdev_region(devid, LED_MAX_NUM, LED_DEV_NAME);
    } else {
        rc = alloc_chrdev_region(&devid, 0, LED_MAX_NUM, LED_DEV_NAME);
        led_major = MAJOR(devid);
    }
    if(rc < 0)
        goto chrdev_failed;
    
    cdev_init(&led_cdev, &led_fops);
    rc = cdev_add(&led_cdev, devid, LED_MAX_NUM);
    if(rc < 0)
        goto cdev_failed;
    led_class = class_create(THIS_MODULE, LED_CLASS_NAME);
    if(IS_ERR(led_class))
        goto class_failed;
    return 0;
class_failed:
    cdev_del(&led_cdev);
cdev_failed:
    unregister_chrdev_region(devid, LED_MAX_NUM);
chrdev_failed:
    return -1;
}
static void __exit chrdev_led_exit(void)
{
    class_destroy(led_class);
    cdev_del(&led_cdev);
    unregister_chrdev_region(MKDEV(led_major, 0), LED_MAX_NUM);
}
int create_led_device_node(int minor, const char *name, void *priv)
{
    struct device *dev = NULL;
    if(minor >= LED_MAX_NUM)
        return NULL;
    
    //device_create take the platform device's private data(priv) as it's own private data.
    if(name)
        dev = device_create(led_class, NULL, MKDEV(led_major, minor), priv, "%s", name);
    else
        dev = device_create(led_class, NULL, MKDEV(led_major, minor), priv, "led-%d", minor);
    if(!dev)
        return -1;
    return 0;
}
void destroy_led_device_node(int minor)
{
    device_destroy(led_class, MKDEV(led_major, minor));
}
struct alpha_led_operations * get_alpha_led_ops(void)
{
    return &alpha_led_ops;
}
EXPORT_SYMBOL(create_led_device_node);
EXPORT_SYMBOL(destroy_led_device_node);
EXPORT_SYMBOL(get_alpha_led_ops);
module_init(chrdev_led_init);
module_exit(chrdev_led_exit);
MODULE_AUTHOR("David");
MODULE_LICENSE("GPL");