Recently, in an A33 Android 4.4 project, we met a customer's request to use gpio to simulate the effect of breathing lights. We all know that Linux has task scheduling, so I tried to use misc device to provide ioctl interface, and did logic processing in HAL layer. The result is not ideal, and there will be a flashing effect when breathing. If an APP is opened, the flashing effect is more obvious, and there is no guarantee of breathing lights at all Because of the delay of system call and task scheduling, and even in HAL, the CPU core that binds the logical thread to handle the effect of breathing light, the result can only be improved a little, but still can't meet the requirements of customers. I don't need to say much. In the end, I use hrtimer in Linux to realize the effect of gpio simulated breathing lamp. The effect is very good!!
The implementation code is as follows:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/slab.h>
#include <linux/stddef.h>
/* mach/sys_config.h for allwinner platform */
#include <mach/sys_config.h>
struct analog_pwm_t{
struct hrtimer hrtimer;
ktime_t kt;
unsigned long long peroid; /* ns */
unsigned long active_zone; /* pwm Effective zone */
unsigned long dead_zone; /* pwm dead zone */
unsigned long total_zone; /* pwm Period = total "zone * hrtimer" tick "accuracy */
unsigned int hrtimer_tick; /* hrimer Tick count of */
unsigned long hrtimer_tick_accuracy; /* pwm Accuracy */
int gpio; /* gpio number */
u32 f_polarity; /* pwm Polarity */
u32 f_hrtimer_tick_reset;
u32 f_set_pwm_dead; /* Set pwm dead band flag */
};
static struct analog_pwm_t *g_analog_pwm = NULL;
static int fetch_sysconfig(void)
{
int err = 0;
u32 led_used = 0;
script_item_u val;
script_item_value_type_e type;
type = script_get_item("led_para", "led_used", &val);
if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
printk(KERN_ERR "%s script_parser_fetch \"led_para\" led_used = %d\n",
__FUNCTION__, val.val);
err = -1;
goto exit;
}
led_used = val.val;
if(!led_used) {
printk(KERN_ERR "%s led is not used in config\n", __FUNCTION__);
err = -2;
goto exit;
}
type = script_get_item("led_para", "led_rd", &val);
if(SCIRPT_ITEM_VALUE_TYPE_PIO != type) {
printk(KERN_ERR "led_rd gpio ctrl io type err!");
err = -1;
goto exit;
}else{
g_analog_pwm->gpio = val.gpio.gpio;
if(0 != gpio_request(g_analog_pwm->gpio, "red led pwm")) {
printk(KERN_ERR "ERROR: analog_pwm red led c gpio_request is failed\n");
err = -1;
goto exit;
}
}
gpio_direction_output(g_analog_pwm->gpio, 0);
return err;
exit:
return err;
}
static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer)
{
if(g_analog_pwm->hrtimer_tick == g_analog_pwm->active_zone && !g_analog_pwm->f_set_pwm_dead){
g_analog_pwm->active_zone++; /* Increase the effective area of pwm */
g_analog_pwm->dead_zone = g_analog_pwm->total_zone - g_analog_pwm->active_zone; /* Calculate the pwm dead time = g ﹣ anlog ﹣ pwm - > dead area * g ﹣ anlog ﹣ pwm - > KT */
g_analog_pwm->f_set_pwm_dead = true; /* pwm deadband should be set next time */
g_analog_pwm->hrtimer_tick = 0; /* Count clear 0 */
//printk("[PWM] =================>total = %d, active = %d\n", g_analog_pwm->total_zone, g_analog_pwm->active_zone);
/* If the dead time is 0, it means that the polarity of pwm will be reversed at the end of a respiratory cycle */
if(g_analog_pwm->dead_zone == 0){
g_analog_pwm->active_zone = 0;
g_analog_pwm->f_hrtimer_tick_reset = true;
g_analog_pwm->f_polarity = !g_analog_pwm->f_polarity;
}
if(g_analog_pwm->f_polarity) /* pwm Polarity judgement */
gpio_set_value(g_analog_pwm->gpio, 1);
else
gpio_set_value(g_analog_pwm->gpio, 0);
}
if(g_analog_pwm->hrtimer_tick == g_analog_pwm->dead_zone && g_analog_pwm->f_set_pwm_dead){
g_analog_pwm->f_set_pwm_dead = false; /* Next time, we should set pwm effective area */
g_analog_pwm->f_hrtimer_tick_reset = true;
//printk("[PWM] =================>total = %d, dead = %d\n", g_analog_pwm->total_zone, g_analog_pwm->dead_zone);
//printk("[PWM] =================> one cycle =================> \n");
if(g_analog_pwm->f_polarity)
gpio_set_value(g_analog_pwm->gpio, 0);
else
gpio_set_value(g_analog_pwm->gpio, 1);
}
g_analog_pwm->kt = ktime_set(0, g_analog_pwm->hrtimer_tick_accuracy);
hrtimer_forward_now(&g_analog_pwm->hrtimer, g_analog_pwm->kt);
/* The purpose of this paper is not to increase the count of G ﹣ anlog ﹣ PWM - > hrtimer ﹣ tick one more time */
if(!g_analog_pwm->f_hrtimer_tick_reset){
g_analog_pwm->hrtimer_tick++;
}else{
g_analog_pwm->hrtimer_tick = 0;
g_analog_pwm->f_hrtimer_tick_reset = false;
}
return HRTIMER_RESTART;
}
static void analog_pwm_start(void)
{
hrtimer_init(&g_analog_pwm->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
g_analog_pwm->hrtimer.function = hrtimer_handler;
g_analog_pwm->kt = ktime_set(0, 10000);
hrtimer_start(&g_analog_pwm->hrtimer, g_analog_pwm->kt, HRTIMER_MODE_REL);
pr_info("[assert] analog_pwm_start ... \n");
}
static int __init hrtimer_gpio_pwm_init(void)
{
int ret = 0;
g_analog_pwm = kzalloc(sizeof(struct analog_pwm_t), GFP_KERNEL);
if(g_analog_pwm == NULL){
pr_err("Function:%s kzalloc failed!\n", __func__);
return -ENOMEM;
}
g_analog_pwm->peroid = 5120000; /* ns */
g_analog_pwm->f_polarity = 0;
g_analog_pwm->total_zone = 256;
g_analog_pwm->hrtimer_tick_accuracy = g_analog_pwm->peroid / g_analog_pwm->total_zone;
printk("hrtimer_gpio_pwm_init: count_accuracy = %u\n", g_analog_pwm->hrtimer_tick_accuracy);
ret = fetch_sysconfig();
if(ret){
kfree(g_analog_pwm);
if(ret == -2)
printk("%s led is not used in sys_config.fex !!!", __func__);
else
printk("fetch_sysconfig is failed!\n");
return -1;
}
analog_pwm_start();
return 0;
}
static void __exit hrtimer_gpio_pwm_exit(void)
{
hrtimer_cancel(&g_analog_pwm->hrtimer);
gpio_set_value(g_analog_pwm->red_led_gpio, 0);
gpio_free(g_analog_pwm->red_led_gpio);
kfree(g_analog_pwm);
}
module_init(hrtimer_gpio_pwm_init);
module_exit(hrtimer_gpio_pwm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("orange@sochip, assert");
MODULE_DESCRIPTION("A driver uses hrtimer to let gpio simulate Pwm");