记录一个自己动手写的代码》充电指示灯驱动
代码实现功能:充电时,指示灯会随着屏幕的亮灭而变化。
LCD 亮 LED灭,LCD灭 LED亮,
拔掉充电器指示灯灭,
充电到100%电量时灭
关机充电和开机充电都如此
/*****************************************************************************
* Included header files
*****************************************************************************/
#include <linux/of_device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_fdt.h>
#include <linux/notifier.h>
#include <linux/power_supply.h>
#include <linux/kthread.h>
#include "mtk_panel_ext.h"
#include "mtk_disp_notify.h"
#include "mtk_charger.h"
#if IS_ENABLED(CONFIG_TCPC_CLASS)
#include "tcpm.h"
#endif
#define BATTERY_UI_FULL 100
#define BATTERY_UI_NOFULL 99
#define CHG_LED_OFF 0
#define CHG_LED_ON 1
struct charger_led *info;
static int chg_gpio;
static bool full_flag = false;
static bool event_loop_thread_stop;
static bool tcpc_status = false;
static struct task_struct *bat_get_tsk;
static wait_queue_head_t event_loop_wait_que;
static atomic_t pending_event = ATOMIC_INIT(0);
static struct workqueue_struct *get_tcpc_dev_wq;
unsigned int get_tcpc_retry = 5;
struct charger_led {
struct device *dev;
struct notifier_block dsp_event_block;
#if IS_ENABLED(CONFIG_TCPC_CLASS)
struct tcpc_device *tcpc_dev;
struct notifier_block tcpc_nb;
struct delayed_work get_tcpc_dev_work;
#endif
};
static bool is_charger_on_flag(void)
{
struct mtk_charger *info = NULL;
info = get_mtk_charger();
if(!info) {
pr_info("[%s]: mtk charger is not ready!\n", __func__);
return false;
}
if (info->chr_type == POWER_SUPPLY_TYPE_UNKNOWN)
return false;
return true;
}
static int flashled_onoff_charger(int val)
{
int set_val = 0;
set_val = !val;
val = !!val;
if(chg_gpio) {
if (set_val == gpio_get_value(chg_gpio))
return 0;
pr_info("%s set gpio166 val = %d\n", __func__, !val);
gpio_set_value(chg_gpio,!val);
}
return 0;
}
static int get_ui_soc(void)
{
union power_supply_propval prop;
struct power_supply *bat_psy = NULL;
prop.intval = 0;
bat_psy = power_supply_get_by_name("battery");
if (IS_ERR_OR_NULL(bat_psy)) {
pr_err("%s get bat psy failed\n", __func__);
return 0;
}
power_supply_get_property(bat_psy,
POWER_SUPPLY_PROP_CAPACITY, &prop);
return prop.intval;
}
/* if it is fully chargerd and power on ,there is issue*/
static int bat_get_thread_fn(void *arg)
{
int bat_val = 0;
int ret = 0;
while (true) {
ret = wait_event_interruptible(event_loop_wait_que,
atomic_read(&pending_event) |
event_loop_thread_stop);
bat_val = get_ui_soc();
if (bat_val >= BATTERY_UI_FULL && full_flag == false) {
full_flag = true;
flashled_onoff_charger(CHG_LED_OFF);
} else if (bat_val <= BATTERY_UI_NOFULL)
full_flag = false;
msleep(2000);
if (kthread_should_stop() || event_loop_thread_stop || ret) {
pr_info("%s exits(%d)\n", __func__, ret);
break;
}
}
return 0;
}
#if IS_ENABLED(CONFIG_TCPC_CLASS)
static int chg_led_tcpc_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct tcp_notify *noti = data;
bool vbus_on;
switch (event) {
case TCP_NOTIFY_SOURCE_VBUS:
vbus_on = (noti->vbus_state.mv) ? true : false;
if (vbus_on) {
pr_info("%s is otg mode", __func__);
flashled_onoff_charger(CHG_LED_OFF);
}
case TCP_NOTIFY_TYPEC_STATE:
if (noti->typec_state.old_state == TYPEC_UNATTACHED &&
(noti->typec_state.new_state == TYPEC_ATTACHED_SNK ||
noti->typec_state.new_state == TYPEC_ATTACHED_NORP_SRC ||
noti->typec_state.new_state == TYPEC_ATTACHED_CUSTOM_SRC ||
noti->typec_state.new_state == TYPEC_ATTACHED_DBGACC_SNK)) {
pr_info("%s plug in", __func__);
atomic_inc(&pending_event);
wake_up_interruptible(&event_loop_wait_que);
tcpc_status = true;
}
else if ((noti->typec_state.old_state == TYPEC_ATTACHED_SRC ||
noti->typec_state.old_state == TYPEC_ATTACHED_SNK ||
noti->typec_state.old_state == TYPEC_ATTACHED_NORP_SRC ||
noti->typec_state.old_state == TYPEC_ATTACHED_CUSTOM_SRC ||
noti->typec_state.old_state == TYPEC_ATTACHED_DBGACC_SNK) &&
noti->typec_state.new_state == TYPEC_UNATTACHED) {
tcpc_status = false;
pr_info("%s plug out", __func__);
atomic_set(&pending_event, 0);
}
else
pr_info("%s is_otg is false", __func__);
}
return NOTIFY_OK;
}
static void chg_led_tcpc_init(struct work_struct *work)
{
struct tcpc_device *tcpc_dev;
struct device_node *np = info->dev->of_node;
const char *tcpc_name;
int ret;
ret = of_property_read_string(np, "tcpc", &tcpc_name);
if (ret < 0){
pr_err("%s get tcpc form dts fail\n", __func__);
goto retry;
}
tcpc_dev = tcpc_dev_get_by_name(tcpc_name);
if (!tcpc_dev) {
pr_err("%s get tcpc device fail\n", __func__);
goto retry;
}
info->tcpc_nb.notifier_call = chg_led_tcpc_notifier;
ret = register_tcp_dev_notifier(tcpc_dev, &info->tcpc_nb,
TCP_NOTIFY_TYPE_USB | TCP_NOTIFY_TYPE_MISC);
if (ret < 0) {
pr_err("%s register notifer fail\n", __func__);
goto retry;
}
info->tcpc_dev = tcpc_dev;
return;
retry:
if(get_tcpc_retry--)
queue_delayed_work(get_tcpc_dev_wq, &info->get_tcpc_dev_work, msecs_to_jiffies(100));
return;
}
#endif
static void chg_led_lcmoff_switch(bool onoff)
{
bool is_charger_on = false;
bool lcd_switch_flag = false;
lcd_switch_flag = onoff;
is_charger_on = is_charger_on_flag();
pr_info("%s: lcm_onoff:%d, is_charger_on:%d, full_flag:%d\n", __func__, onoff,is_charger_on, full_flag);
if (is_charger_on && !lcd_switch_flag && !full_flag) {
pr_info("%s chg_led on\n", __func__);
flashled_onoff_charger(CHG_LED_ON);
} else if ((is_charger_on && lcd_switch_flag) || full_flag) {
pr_info("%s chg_led off\n", __func__);
flashled_onoff_charger(CHG_LED_OFF);
} else if (!is_charger_on) {
pr_info("%s chg_led off\n", __func__);
flashled_onoff_charger(CHG_LED_OFF);
}
}
static int chg_led_disp_notifier_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
int *blank = (int *)data;
if (!blank) {
pr_err("Invalid blank\n");
return -1;
}
pr_info("%s event=%lu, *blank=%d \n", __func__, event, *blank);
if (event == MTK_DISP_EARLY_EVENT_BLANK) {
if (*blank == MTK_DISP_BLANK_POWERDOWN)
chg_led_lcmoff_switch(false); //suspend
if (atomic_read(&pending_event) == 0 && tcpc_status == true) {
pr_info("%s wakeup bat thread", __func__);
atomic_inc(&pending_event);
wake_up_interruptible(&event_loop_wait_que);
}
} else if (event == MTK_DISP_EVENT_BLANK) {
if (*blank == MTK_DISP_BLANK_AOD_NOTIFY) //esd resume
chg_led_lcmoff_switch(true); //resume
}
return NOTIFY_OK;
}
static int chg_led_probe(struct platform_device *pdev)
{
// struct charger_led *info = NULL;
int ret = 0;
struct device *dev = &pdev->dev;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = dev;
chg_gpio = of_get_named_gpio(dev->of_node, "chg_gpio", 0);
if(gpio_is_valid(chg_gpio)) {
ret = gpio_request(chg_gpio,"chg_en");
if(!ret)
flashled_onoff_charger(CHG_LED_OFF);
else {
pr_err("%s gpio_request failed, ret =%d\n", __func__, ret);
return ret;
}
} else {
pr_err("%s get chg_en gpio failed", __func__);
return -ENODEV;
}
bat_get_tsk = kthread_create(
bat_get_thread_fn, NULL, "leds_charger");
init_waitqueue_head(&event_loop_wait_que);
atomic_set(&pending_event, 0);
wake_up_process(bat_get_tsk);
#if IS_ENABLED(CONFIG_TCPC_CLASS)
/* tcpc */
get_tcpc_dev_wq = alloc_workqueue("get_tcpc_dev_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
if (!get_tcpc_dev_wq) {
pr_err("get_tcpc_dev_wq create workqueue failed\n");
return -ENOMEM;
}
INIT_DELAYED_WORK(&info->get_tcpc_dev_work, chg_led_tcpc_init);
queue_delayed_work(get_tcpc_dev_wq, &info->get_tcpc_dev_work, 0);
#endif
info->dsp_event_block.notifier_call = chg_led_disp_notifier_callback;
ret = mtk_disp_notifier_register("Carlos chg_led", &info->dsp_event_block);
if (ret) {
pr_err("%s Failed to register disp notifier client:%d", __func__, ret);
gpio_free(chg_gpio);
}
platform_set_drvdata(pdev, info);
pr_info("%s 0817 success\n", __func__);
return 0;
}
static int chg_led_remove(struct platform_device *dev)
{
// struct charger_led *info = platform_get_drvdata(dev);
int ret = 0;
if (gpio_is_valid(chg_gpio))
gpio_free(chg_gpio);
if (mtk_disp_notifier_unregister(&info->dsp_event_block))
pr_err("Error occurred while unregistering disp_notifier.\n");
if(!IS_ERR_OR_NULL(info->tcpc_dev))
return 0;
ret = unregister_tcp_dev_notifier(info->tcpc_dev, &info->tcpc_nb,
TCP_NOTIFY_TYPE_USB | TCP_NOTIFY_TYPE_MISC);
if (ret < 0)
pr_err("Error occurred while unregistering tcpc_notifier.\n");
event_loop_thread_stop = true;
wake_up_interruptible(&event_loop_wait_que);
kthread_stop(bat_get_tsk);
return ret;
}
static void chg_led_shutdown(struct platform_device *dev)
{
pr_info("%s \n", __func__);
}
static const struct of_device_id of_chg_leds_match[] = {
{ .compatible = "charger-leds", },
{},
};
MODULE_DEVICE_TABLE(of, of_chg_leds_match);
static struct platform_driver chg_led_driver = {
.probe = chg_led_probe,
.remove = chg_led_remove,
.shutdown = chg_led_shutdown,
.driver = {
.name = "leds-gpio",
.of_match_table = of_chg_leds_match,
},
};
module_platform_driver(chg_led_driver);
MODULE_AUTHOR("Carlos Driver Team");
MODULE_DESCRIPTION("Carlos GPIO Charger LEDS Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-gpio");