1, Foreword
In order to facilitate the compatibility and unified management of optical distance sensing drivers, all optical distance sensing drivers are placed in the alsps framework;
In this paper, the optical distance sensing ltr559 under the alsps framework of MT8183 platform is analyzed as an example;
Documents and directories involved: Device tree: kernel-4.4/arch/arm64/boot/dts/mediatek/mt6771.dts kernel-4.4/arch/arm64/boot/dts/mediatek/k71v1_64_bsp.dts alsps framework: kernel-4.4/drivers/misc/mediatek/sensors-1.0/alsps/alsps.c kernel-4.4/drivers/misc/mediatek/sensors-1.0/hwmon/sensor_attributes/sensor_attr.c Optical distance sensing drive: kernel-4.4/drivers/misc/mediatek/sensors-1.0/alsps/LTR559/ltr559.c
2, Analysis of alsps framework
1. Drive loading sequence
ltr599.c and alsps The calling order of C driver in the process of kernel initialization is related to the way they choose to load the entry function;
ltr599.c: module_init(ltr559_init);
alsps.c: module_init(ltr559_init);
From kernel-4.4/include/linux/init.com H file can obtain the macro list and sorting of startup order;
#define early_initcall(fn) __define_initcall(fn, early) #define pure_initcall(fn) __define_initcall(fn, 0) #define core_initcall(fn) __define_initcall(fn, 1) #define core_initcall_sync(fn) __define_initcall(fn, 1s) #define postcore_initcall(fn) __define_initcall(fn, 2) #define postcore_initcall_sync(fn) __define_initcall(fn, 2s) #define arch_initcall(fn) __define_initcall(fn, 3) #define arch_initcall_sync(fn) __define_initcall(fn, 3s) #define subsys_initcall(fn) __define_initcall(fn, 4) #define subsys_initcall_sync(fn) __define_initcall(fn, 4s) #define fs_initcall(fn) __define_initcall(fn, 5) #define fs_initcall_sync(fn) __define_initcall(fn, 5s) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) #define device_initcall(fn) __define_initcall(fn, 6) #define device_initcall_sync(fn) __define_initcall(fn, 6s) #define late_initcall(fn) __define_initcall(fn, 7) #define late_initcall_sync(fn) __define_initcall(fn, 7s
module_init corresponds to the default device_initcall, compare to late_ If the initcall number is small, the smaller the number, the higher the priority; So the kernel initialization call sequence is to call ltr559 After the C entrance function, the alsps. is called. The entry function of C;
2. sensor kernel initialization driver process
When the machine starts up and enters the kernel initialization phase, the driving process of optical distance sensor is as follows:
Call ltr559 first Ltr559 in C file_ Init, in alsps_ driver_ In the add function, register an ALS with the kernel platform bus first_ ps_ Device driver of driver structure: Platform_ driver_ Register (& als_ps_driver)), and then call alsps for all loaded optical distance sensing drivers_ driver_ add(obj); Function, alsps_ init_ The members in the list [max_choose_alsps_num] array point to the corresponding obj;
//ltr599.c static struct alsps_init_info ltr559_init_info = { .name = LTR559_DEV_NAME, .init = alsps_local_init, .uninit = alsps_remove, }; static int __init ltr559_init(void) { alsps_driver_add(<r559_init_info); return 0; }
//mt6771.dts //Configure device information under alsps architecture alsps:als_ps@0 { compatible = "mediatek,als_ps"; };
//alsps.c static const struct of_device_id als_ps_of_match[] = { {.compatible = "mediatek,als_ps",}, }; static struct platform_driver als_ps_driver = { .probe = als_ps_probe, .remove = als_ps_remove, .driver = { .name = "als_ps", .of_match_table = als_ps_of_match, } }; int alsps_driver_add(struct alsps_init_info *obj) { int err = 0; int i = 0; for (i = 0; i < MAX_CHOOSE_ALSPS_NUM; i++) { //MAX_CHOOSE_ALSPS_NUM = 5 if ((i == 0) && (alsps_init_list[0] == NULL)) if (platform_driver_register(&als_ps_driver)) if (alsps_init_list[i] == NULL) { obj->platform_diver_addr = &als_ps_driver; alsps_init_list[i] = obj; break; } } //When the number of optical distance sensing drive traverses exceeds 5 (MAX_CHOOSE_ALSPS_NUM), an error will be reported if (i >= MAX_CHOOSE_ALSPS_NUM) { pr_err("ALSPS driver add err\n"); err = -1; } return err; }
When alsps_ init_ After the list array member is bound to all optical distance sensing obj, start running alsps Alsps in C_ init->alsps_ probe->alsps_ real_ driver_ init->alsps_init_list[i]->init();, By operating alsps_ init_ The init function pointer of the list array member to run the init pointer function of the obj structure in the corresponding optical distance sensing driver, such as ltr559 Alsps in C_ local_ Init function;
After that, I became familiar with the process and registered ltr559 with the I2C platform_ i2c_ Driver, when alsps_ of_ If the compatible attribute in match is consistent with that in dts, ltr559 will be called_ i2c_ probe;
//k71v1_64_bsp.dts &i2c1 { alsps1@3b{ compatible = "mediatek,alsps559"; reg = <0x3b>; }; }
//ltr559.c static const struct of_device_id alsps_of_match[] = { {.compatible = "mediatek,alsps559"}, {}, }; static struct i2c_driver ltr559_i2c_driver = { .probe = ltr559_i2c_probe, .of_match_table = alsps_of_match, ... }; static int alsps_local_init(void) { i2c_add_driver(<r559_i2c_driver); //Register the platform under the i2c bus_ driver }
Taking the light sense as an example, in ltr559_ i2c_ The probe function performs the following:
1. Complete the initialization of light sensing hardware (not involving alsps architecture, which will be discussed in Chapter 3);
2. Complete the light sensing function control, register the light sensing device, and create the device node: als_register_control_path;
3. Add data transfer function in alsps framework file: als_register_data_path;
2.1 analysis als_register_control_path function
In ALS_ register_ control_ The path function implements two functions:
1,alsps. General function function and ltr559 under alsps architecture in C ALS in C_ CTL structure member binding, through this connection in alsps C can control the enable switch of ltr559 light sensing, and can also read ltr559 C. whether the data reporting function of alsps architecture is selected for the file;
//ltr559.c static int ltr559_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct als_control_path als_ctl={0}; als_ctl.open_report_data= als_open_report_data;//Not used als_ctl.enable_nodata = ltr559_als_power; //Enable ltr559 light sense ... als_ctl.is_report_input_direct = false;//false means that the data is not directly reported and the alsps architecture process is followed als_register_control_path(&als_ctl); } //alsps.c int als_register_control_path(struct als_control_path *ctl) { cxt = alsps_context_obj; //1. Function binding cxt->als_ctl.open_report_data = ctl->open_report_data; cxt->als_ctl.enable_nodata = ctl->enable_nodata; ... cxt->als_ctl.is_report_input_direct = ctl->is_report_input_direct; //2. Register device node als_misc_init(alsps_context_obj); sysfs_create_group(&alsps_context_obj->als_mdev.this_device->kobj, &als_attribute_group); kobject_uevent(&alsps_context_obj->als_mdev.this_device->kobj, KOBJ_ADD); return 0; }
2. Create and register light sensing device node: / dev/m_als_misc, a fops structure provided to the upper layer to obtain data;
//alsps.c static const struct file_operations light_fops = { .owner = THIS_MODULE, .open = light_open, .read = light_read, .poll = light_poll, }; #define ALS_MISC_DEV_NAME "m_als_misc" static int als_misc_init(struct alsps_context *cxt) { cxt->als_mdev.minor = ID_LIGHT; cxt->als_mdev.name = ALS_MISC_DEV_NAME; cxt->als_mdev.fops = &light_fops; sensor_attr_register(&cxt->als_mdev); } //sensor_attr.c int sensor_attr_register(struct sensor_attr_t *misc) { list_for_each_entry(c, &sensor_attr_list, list) //Main equipment No.: sensor_attr_major; Secondary equipment No.: misc - > minor dev = MKDEV(sensor_attr_major, misc->minor); //Create a distance sensitive character device node according to the device number generated by dev: / dev/m_als_misc misc->this_device = device_create(sensor_attr_class, misc->parent, dev, misc, "%s", misc->name); //misc->name = "m_als_misc" list_add(&misc->list, &sensor_attr_list); //Put misc - > list into kernel sensor_attr_list in the linked list; sensor_event_register(misc->minor); //Subsequent reanalysis }
2.2 analysis als_register_data_path function
als_ register_ data_ The function of path function is mainly in alsps C file through the operation get_ The data function pointer can get ltr559 C) light sensitivity value in the document;
//ltr559.c static int ltr559_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { als_data.get_data = ltr559_als_read; //ltr559_ als_ The function of the read function is to read the data sensed by the light sensing chip through i2c, and then point the parameters of the function to the obtained value; als_data.vender_div = 100; als_register_data_path(&als_data); } //alsps.c int als_register_data_path(struct als_data_path *data) { struct alsps_context *cxt = NULL; cxt = alsps_context_obj; cxt->als_data.get_data = data->get_data;//get_data is the pointer to obtain the light sensing function cxt->als_data.vender_div = data->vender_div; if (cxt->als_data.get_data == NULL) return -1; return 0; }
3. Operation flow of sys node control sensor under alsps framework
The enable switch and data reporting of optical distance sensing are controlled by the hal layer by operating the sys node. The following is an example of optical distance sensing als;
3.1 initialize the timer for reading light sensing data_ als
A light sense timer is defined during alsps initialization_ ALS (this timer initialization phase is not started). When the timer starts, first obtain the light sensing value and then report the data, and cycle to collect and report the data every other period of time (200ms by default);
//alsps.c /*When timer_ After the ALS timer is triggered, ALS in the work queue will be called after timeout_ work_ Func function; Functions: 1. Obtain ltr559 C) light sensing value induced in; 2,Place the light sensing value in the cache buffer and wait for the application layer to read; 3,Modify the timer time to ensure that the timer cycle is triggered continuously; */ static void als_work_func(struct work_struct *work) { cxt->als_data.get_data(&value, &status); //Get ltr559 C the light sensing value read in the file; als_data_report(cxt->drv_data.als_data.values[0], cxt->drv_data.als_data.status);//Report data mod_timer(&cxt->timer_als, jiffies + atomic_read(&cxt->delay_als) / (1000 / HZ));//Modify the timer interval (& CXT - > delay_als) ms and start it } //When timer timer_als is called when ALS is timed out_ Poll function static void als_poll(unsigned long data) { //Submit a task to a work queue schedule_work(&obj->report_als); } static struct alsps_context *alsps_context_alloc_object(void) { atomic_set(&obj->delay_als, 200); //obj->delay_als = 200 INIT_WORK(&obj->report_als, als_work_func); //Initialize work queue init_timer(&obj->timer_als);//Initialize timer_als timer obj->timer_als.expires = jiffies + atomic_read(&obj->delay_als)/(1000/HZ); obj->timer_als.function = als_poll; obj->timer_als.data = (unsigned long)obj; //The following parameter values will be used to judge whether to start the timer obj->is_als_first_data_after_enable = false; obj->is_als_polling_run = false; obj->is_als_batch_enable = false; obj->als_power = 0; obj->als_enable = 0; obj->als_delay_ns = -1; obj->als_rgb_delay_ns = -1; obj->als_latency_ns = -1; }
3.2 trigger timer_ Process of ALS
timer_ The ALS timer is triggered by alsps The write function of alsactive and alsbatch nodes in C file is controlled by als_store_active,als_store_batch;
3.2.1 in ALS_ enable_ and_ Start timer in batch function_ ALS timer meets condition A:
1,cxt->als_power == 0 && cxt->als_enable == 1
==>cxt->als_ The default initial value of power is 0, CXT - > ALS_ Enable in als_store_active function has settings;
2,cxt->als_delay_ns >= 0
==>In ALS_ store_ There are settings in the batch function
3,cxt->als_ctl.is_report_input_direct == false
==>According to ltr559 CTL - > is in C_ report_ input_ Determined by the variable of direct;
4,cxt->is_als_polling_run == false
==>The default initial value is false, which meets the conditions;
At ltr559 Is in C_ report_ input_ When direct is set to false by default:
echo 4,0,200000000,0 > /sys/class/sensor/m_ als_ Misc / alsbatch, setting als_delay_ns=200000000 //200ms
echo 4,1 > /sys/class/sensor/m_ als_ Misc / alsactive, setting als_enable =1;
If condition A is met, open the timer to report data;
3.2.2 in ALS_ enable_ and_ Cancel timer in batch function_ ALS timer meets condition B:
1,cxt->als_power == 1 && cxt->als_enable == 0
2,cxt->als_ctl.is_report_input_direct == false && cxt->is_als_polling_run == true
When timer_ When the ALS timer is turned on, the second condition has been met. You only need to set als_enable =0 to close the timer;
echo 4,0 > /sys/class/sensor/m_als_misc/alsactive
//alsps.c static ssize_t als_store_active(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%d,%d", &handle, &en); if (handle == ID_LIGHT) { //ID_LIGHT = 4 if (en) { cxt->als_enable = 1; last_als_report_data = -1; } else if (!en) { cxt->als_enable = 0; } als_enable_and_batch(); } //alsps.c static ssize_t als_store_batch(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%d,%d,%lld,%lld", &handle, &flag, &delay_ns, &latency_ns); if (handle == ID_LIGHT) { //ID_LIGHT = 4 cxt->als_delay_ns = delay_ns; cxt->als_latency_ns = latency_ns; err = als_enable_and_batch(); } else if (handle == ID_RGBW) { //ID_RGBW = 69 cxt->als_rgb_delay_ns = delay_ns; cxt->rgbw_latency_ns = latency_ns; } } DEVICE_ATTR(alsactive, S_IWUSR | S_IRUGO, als_show_active, als_store_active); DEVICE_ATTR(alsbatch, S_IWUSR | S_IRUGO, als_show_batch, als_store_batch);
//alsps.c static int als_enable_and_batch(void) { mod_timer(&cxt->timer_als,jiffies + atomic_read(&cxt->delay_als) /(1000 / HZ)); } static int als_enable_and_batch(void) { /* als_power on -> power off */ if (cxt->als_power == 1 && cxt->als_enable == 0) { if (cxt->als_ctl.is_report_input_direct == false &&cxt->is_als_polling_run == true) { del_timer_sync(&cxt->timer_als); //Delete timer_als light sense timer cancel_work_sync(&cxt->report_als); cxt->drv_data.als_data.values[0] = ALSPS_INVALID_VALUE; cxt->is_als_polling_run = false; } cxt->als_ctl.enable_nodata(0); cxt->als_power = 0; cxt->als_delay_ns = -1; return 0; } /* als_power off -> power on */ if (cxt->als_power == 0 && cxt->als_enable == 1) { cxt->als_ctl.enable_nodata(1); cxt->als_power = 1; } if (cxt->als_power == 1 && cxt->als_delay_ns >= 0) { if (cxt->als_ctl.is_support_batch) cxt->als_ctl.batch(0, cxt->als_delay_ns,cxt->als_latency_ns); else cxt->als_ctl.batch(0, cxt->als_delay_ns, 0); if (cxt->als_ctl.is_report_input_direct == false) { int mdelay = cxt->als_delay_ns; do_div(mdelay, 1000000); atomic_set(&cxt->delay_als, mdelay); if (cxt->is_als_polling_run == false) { mod_timer(&cxt->timer_als,jiffies + atomic_read(&cxt->delay_als) /(1000 / HZ));//Set timer_als light sense timer interval and start cxt->is_als_polling_run = true; cxt->is_als_first_data_after_enable = true; } } } return 0; }
Note: the heavy sensing accel framework of mtk platform is similar to the alsps framework of optical distance sensing. To analyze the heavy sensing accel framework, you can go to kernel-4.4/drivers/misc/mediatek/sensors-1.0/accelerometer/accel C) browsing;