【充电指示灯驱动】

记录一个自己动手写的代码》充电指示灯驱动

代码实现功能:充电时,指示灯会随着屏幕的亮灭而变化。

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");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个专注于USB的驱动工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值