kernel_samsung_a34x-permissive/drivers/power/supply/mediatek/charger/afc_charger.c

943 lines
21 KiB
C
Raw Normal View History

/* afc_charger.c
*
* Copyright (C) 2020 Samsung Electronics
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#define pr_fmt(fmt) "[AFC] " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#if defined(CONFIG_DRV_SAMSUNG)
#include <linux/sec_class.h>
#endif
#if defined(CONFIG_SEC_PARAM)
#include <linux/sec_ext.h>
#endif
#include <linux/ktime.h>
#include <linux/pinctrl/consumer.h>
#include <mt-plat/v1/mtk_battery.h>
#include <mt-plat/v1/charger_type.h>
#include <mt-plat/mtk_boot_common.h>
#include <linux/muic/afc_gpio/gpio_afc_charger.h>
#include "../drivers/misc/mediatek/typec/tcpc/inc/mt6360.h"
#include "../drivers/misc/mediatek/typec/tcpc/inc/tcpci.h"
#ifdef CONFIG_BATTERY_SAMSUNG
#include <dt-bindings/battery/sec-battery.h>
#include <linux/power_supply.h>
#include <../drivers/battery/common/sec_charging_common.h>
#include <../drivers/battery/common/sec_battery.h>
#endif
#if IS_ENABLED(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/common/muic_notifier.h>
#endif
#if IS_ENABLED(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/common/muic_notifier.h>
extern struct muic_platform_data muic_pdata;
#if IS_ENABLED(CONFIG_VIRTUAL_MUIC)
#include <linux/muic/common/vt_muic/vt_muic.h>
#endif
#endif
#if defined(CONFIG_PDIC_NOTIFIER)
#include <linux/usb/typec/common/pdic_notifier.h>
#if defined(CONFIG_PDIC_USE_MODULE_PARAM)
#include <linux/usb/typec/common/pdic_param.h>
#endif
#if IS_ENABLED(CONFIG_BATTERY_NOTIFIER)
#include <linux/battery/battery_notifier.h>
#else
#include <linux/battery/sec_pd.h>
#endif
extern struct pdic_notifier_struct pd_noti;
#endif
struct gpio_afc_ddata *g_ddata;
#if IS_ENABLED(CONFIG_SEC_HICCUP)
static int gpio_hiccup;
#endif
/* afc_mode:
* 0x31 : Disabled
* 0x30 : Enabled
*/
static int afc_mode;
static int __init set_charging_mode(char *str)
{
int mode;
get_option(&str, &mode);
afc_mode = (mode & 0x0000FF00) >> 8;
pr_info("%s: afc_mode is 0x%02x\n", __func__, afc_mode);
return 0;
}
early_param("charging_mode", set_charging_mode);
void set_attached_afc_dev(int attached_afc_dev)
{
#if IS_ENABLED(CONFIG_MUIC_NOTIFIER)
#if IS_ENABLED(CONFIG_VIRTUAL_MUIC)
vt_muic_set_attached_afc_dev(attached_afc_dev);
#else
muic_notifier_attach_attached_dev(attached_afc_dev);
#endif
#endif /* CONFIG_MUIC_NOTIFIER */
}
static int _get_afc_mode(void)
{
return afc_mode;
}
#if 0
static void afc_udelay(int delay)
{
s64 start = 0, duration = 0;
start = ktime_to_us(ktime_get());
udelay(delay);
duration = ktime_to_us(ktime_get()) - start;
if (duration > delay + 20)
pr_err("%s %dus -> %dus\n", __func__, delay, duration);
}
#else
#define afc_udelay(d) udelay(d)
#endif
static void gpio_afc_send_parity_bit(struct gpio_afc_ddata *ddata, int data)
{
int cnt = 0, i = 0;
int gpio = ddata->pdata->gpio_afc_data;
for (i = 0; i < 8; i++) {
if (data & (0x1 << i))
cnt++;
}
cnt %= 2;
gpio_set_value(gpio, !cnt);
afc_udelay(UI);
if (!cnt) {
gpio_set_value(gpio, 0);
afc_udelay(SYNC_PULSE);
}
}
static void gpio_afc_sync_pulse(int gpio)
{
gpio_set_value(gpio, 1);
afc_udelay(SYNC_PULSE);
gpio_set_value(gpio, 0);
afc_udelay(SYNC_PULSE);
}
static int gpio_afc_send_mping(struct gpio_afc_ddata *ddata)
{
int gpio = ddata->pdata->gpio_afc_data;
unsigned long flags;
int ret = 0;
s64 start = 0, end = 0;
if (ddata->gpio_input) {
ddata->gpio_input = false;
gpio_direction_output(ddata->pdata->gpio_afc_data, 0);
}
/* send mping */
spin_lock_irqsave(&ddata->spin_lock, flags);
gpio_set_value(gpio, 1);
udelay(MPING);
gpio_set_value(gpio, 0);
start = ktime_to_us(ktime_get());
spin_unlock_irqrestore(&ddata->spin_lock, flags);
end = ktime_to_us(ktime_get());
ret = (int)end-start;
return ret;
}
static int gpio_afc_check_sping(struct gpio_afc_ddata *ddata, int delay)
{
int gpio = ddata->pdata->gpio_afc_data;
int ret = 0;
s64 start = 0, end = 0, duration = 0;
unsigned long flags;
if (delay > (MPING+3*UI)) {
ret = delay - (MPING+3*UI);
return ret;
}
if (!ddata->gpio_input) {
ddata->gpio_input = true;
gpio_direction_input(gpio);
}
spin_lock_irqsave(&ddata->spin_lock, flags);
if (delay < (MPING-3*UI)) {
udelay(MPING-3*UI-delay);
if (!gpio_get_value(gpio)) {
ret = -EAGAIN;
goto out;
}
}
start = ktime_to_us(ktime_get());
while (gpio_get_value(gpio)) {
duration = ktime_to_us(ktime_get()) - start;
if (duration > DATA_DELAY)
break;
udelay(UI);
}
out:
start = ktime_to_us(ktime_get());
spin_unlock_irqrestore(&ddata->spin_lock, flags);
end = ktime_to_us(ktime_get());
if (ret == 0)
ret = (int)end-start;
return ret;
}
static int gpio_afc_send_data(struct gpio_afc_ddata *ddata, int data)
{
int i = 0, ret = 0;
int gpio = ddata->pdata->gpio_afc_data;
unsigned long flags;
s64 start = 0, end = 0;
if (ddata->gpio_input) {
ddata->gpio_input = false;
gpio_direction_output(ddata->pdata->gpio_afc_data, 0);
}
spin_lock_irqsave(&ddata->spin_lock, flags);
udelay(UI);
/* start of transfer */
gpio_afc_sync_pulse(gpio);
if (!(data & 0x80)) {
gpio_set_value(gpio, 1);
afc_udelay(SYNC_PULSE);
}
for (i = 0x80; i > 0; i = i >> 1) {
gpio_set_value(gpio, data & i);
afc_udelay(UI);
}
gpio_afc_send_parity_bit(ddata, data);
gpio_afc_sync_pulse(gpio);
gpio_set_value(gpio, 1);
udelay(MPING);
gpio_set_value(gpio, 0);
start = ktime_to_us(ktime_get());
spin_unlock_irqrestore(&ddata->spin_lock, flags);
end = ktime_to_us(ktime_get());
ret = (int)end-start;
return ret;
}
static int gpio_afc_recv_data(struct gpio_afc_ddata *ddata, int delay)
{
int gpio = ddata->pdata->gpio_afc_data;
int ret = 0, gpio_value = 0, reset = 1;
s64 limit_start = 0, start = 0, end = 0, duration = 0;
unsigned long flags;
if (!ddata->gpio_input) {
ddata->gpio_input = true;
gpio_direction_input(gpio);
}
if (delay > DATA_DELAY+MPING) {
ret = -EAGAIN;
} else if (delay > DATA_DELAY && delay <= DATA_DELAY+MPING) {
gpio_afc_check_sping(ddata, delay-DATA_DELAY);
} else if (delay <= DATA_DELAY) {
spin_lock_irqsave(&ddata->spin_lock, flags);
udelay(DATA_DELAY-delay);
limit_start = ktime_to_us(ktime_get());
while (duration < MPING-3*UI) {
if (reset) {
start = 0;
end = 0;
duration = 0;
}
gpio_value = gpio_get_value(gpio);
if (!gpio_value && !reset) {
end = ktime_to_us(ktime_get());
duration = end - start;
reset = 1;
} else if (gpio_value) {
if (reset) {
start = ktime_to_us(ktime_get());
reset = 0;
}
}
udelay(UI);
if ((ktime_to_us(ktime_get()) - limit_start) > (MPING+DATA_DELAY*2)) {
ret = -EAGAIN;
break;
}
}
spin_unlock_irqrestore(&ddata->spin_lock, flags);
}
return ret;
}
static int gpio_afc_set_voltage(struct gpio_afc_ddata *ddata, u32 voltage)
{
int ret = 0;
ret = gpio_afc_send_mping(ddata);
ret = gpio_afc_check_sping(ddata, ret);
if (ret < 0) {
pr_err("Start Mping NACK\n");
goto out;
}
if (voltage == 0x9)
ret = gpio_afc_send_data(ddata, 0x46);
else
ret = gpio_afc_send_data(ddata, 0x08);
ret = gpio_afc_check_sping(ddata, ret);
if (ret < 0) {
pr_err("sping err2 %d\n", ret);
goto out;
}
ret = gpio_afc_recv_data(ddata, ret);
if (ret < 0)
pr_err("sping err3 %d\n", ret);
ret = gpio_afc_send_mping(ddata);
ret = gpio_afc_check_sping(ddata, ret);
if (ret < 0)
pr_err("End Mping NACK\n");
out:
return ret;
}
static void gpio_afc_reset(struct gpio_afc_ddata *ddata)
{
int gpio = ddata->pdata->gpio_afc_data;
if (ddata->gpio_input) {
ddata->gpio_input = false;
gpio_direction_output(ddata->pdata->gpio_afc_data, 0);
}
/* send mping */
gpio_set_value(gpio, 1);
udelay(RESET_DELAY);
gpio_set_value(gpio, 0);
}
void set_afc_voltage_for_performance(bool enable)
{
struct gpio_afc_ddata *ddata = g_ddata;
ddata->check_performance = enable;
}
static int vbus_level_check(void)
{
int vbus = battery_get_vbus();
pr_info("%s %dmV\n", __func__, vbus);
if (vbus > 3800 && vbus < 6500)
vbus = 0x5;
else if (vbus > 7500 && vbus < 10500)
vbus = 0x9;
else
vbus = 0x0;
return vbus;
}
static void gpio_afc_kwork(struct kthread_work *work)
{
struct gpio_afc_ddata *ddata =
container_of(work, struct gpio_afc_ddata, kwork);
#if defined(CONFIG_BATTERY_SAMSUNG) && !defined(CONFIG_VIRTUAL_MUIC)
struct power_supply *psy = power_supply_get_by_name("battery");
#endif
int ret = 0, i = 0;
int vol = 0;
int retry = 0;
if (!ddata) {
pr_err("driver is not ready\n");
return;
}
if (ddata->is_hiccup_mode == SEC_HICCUP_MODE_ON) {
pr_err("%s hiccup mode is active\n", __func__);
return;
}
vol = ddata->set_voltage;
if (vol == 0x9) {
msleep(1200);
} else {
gpio_afc_reset(ddata);
msleep(200);
}
ret = vbus_level_check();
pr_info("%s set vol[%dV], current[%dV]\n",
__func__, vol, ret);
if (ret == 0 || ddata->is_hiccup_mode == SEC_HICCUP_MODE_ON) {
pr_err("%s disconnected or hiccup mode(%d)\n", __func__,
ddata->is_hiccup_mode);
return;
}
ddata->curr_voltage = ret;
mutex_lock(&ddata->mutex);
__pm_stay_awake(ddata->ws);
if (ret != vol) {
gpio_set_value(ddata->pdata->gpio_afc_switch, 1);
for (retry = 0; retry < AFC_RETRY_CNT; retry++) {
for (i = 0; i < AFC_RETRY_CNT ; i++) {
gpio_afc_set_voltage(ddata, vol);
usleep_range(1000, 1000);
if (ddata->is_hiccup_mode == SEC_HICCUP_MODE_ON)
goto err;
}
ret = vbus_level_check();
ddata->curr_voltage = ret;
pr_info("%s current %dV\n", __func__, ret);
if (ret == vol) {
ret = 0;
pr_info("%s success\n", __func__);
break;
} else if (ret == 0) {
pr_err("%s disconnected\n", __func__);
ret = -EAGAIN;
goto err;
}
ret = -EAGAIN;
pr_err("%s retry %d\n", __func__, retry + 1);
}
} else
ret = 0;
if (vol == 0x9) {
if (!ret)
set_attached_afc_dev(ATTACHED_DEV_AFC_CHARGER_9V_MUIC);
else
set_attached_afc_dev(ATTACHED_DEV_TA_MUIC);
} else if (vol == 0x5) {
if (!ret) {
if (ddata->afc_disable)
set_attached_afc_dev(ATTACHED_DEV_TA_MUIC);
else
set_attached_afc_dev(ATTACHED_DEV_AFC_CHARGER_5V_MUIC);
} else if (vol == 0x9)
set_attached_afc_dev(ATTACHED_DEV_AFC_CHARGER_9V_DUPLI_MUIC);
}
#if defined(CONFIG_BATTERY_SAMSUNG) && !defined(CONFIG_VIRTUAL_MUIC)
if (!IS_ERR_OR_NULL(psy)) {
union power_supply_propval val;
val.intval = (ddata->curr_voltage == 0x9) ? AFC_CHARGER : STANDARD_CHARGER;
pr_info("%s: curr_voltage: %d\n", __func__, ddata->curr_voltage);
if (val.intval == AFC_CHARGER)
val.intval = SEC_BATTERY_CABLE_9V_TA;
else if (ddata->check_performance) {
if (val.intval == STANDARD_CHARGER) {
if (!ret)
val.intval = SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT;
else {
pr_info("%s: couldn't set 5V\n", __func__);
val.intval = SEC_BATTERY_CABLE_9V_TA;
}
}
}
if ((val.intval == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) ||
(val.intval == SEC_BATTERY_CABLE_9V_TA)) {
ret = power_supply_set_property(psy,
POWER_SUPPLY_PROP_ONLINE, &val);
if (ret < 0)
pr_err("%s: Fail to set POWER_SUPPLY_PROP_ONLINE (%d)\n",
__func__, ret);
}
}
#endif
err:
gpio_set_value(ddata->pdata->gpio_afc_switch, 0);
__pm_relax(ddata->ws);
mutex_unlock(&ddata->mutex);
}
int set_afc_voltage(int voltage)
{
struct gpio_afc_ddata *ddata = g_ddata;
int vbus = 0, cur = 0;
if (voltage == 0x9)
set_afc_voltage_for_performance(false);
else if (voltage == 0x5)
set_afc_voltage_for_performance(true);
if (!ddata) {
pr_err("driver is not ready\n");
return -EAGAIN;
}
#if defined(CONFIG_DRV_SAMSUNG)
if (voltage == 0x9 && ddata->afc_disable) {
pr_err("AFC is disabled by USER\n");
return -EINVAL;
}
#endif
kthread_flush_work(&ddata->kwork);
cur = ddata->curr_voltage;
vbus = vbus_level_check();
pr_info("%s %d(%d) => %dV\n", __func__,
ddata->curr_voltage, vbus, voltage);
if (ddata->curr_voltage == voltage) {
if (ddata->curr_voltage == vbus) {
msleep(20);
vbus = vbus_level_check();
if (ddata->curr_voltage == vbus)
return 0;
}
}
ddata->curr_voltage = vbus;
ddata->set_voltage = voltage;
kthread_queue_work(&ddata->kworker, &ddata->kwork);
return 0;
}
#if IS_ENABLED(CONFIG_SEC_HICCUP)
void set_sec_hiccup(bool en)
{
struct gpio_afc_ddata *ddata = g_ddata;
int ret = 0;
#if defined(CONFIG_PDIC_USE_MODULE_PARAM)
if (is_lpcharge_pdic_param()) {
pr_info("%s %d, lpm mode\n", __func__, en);
return;
}
#endif
if (en)
ddata->is_hiccup_mode = SEC_HICCUP_MODE_ON;
else
ddata->is_hiccup_mode = SEC_HICCUP_MODE_OFF;
gpio_set_value(gpio_hiccup, en);
ret = gpio_get_value(gpio_hiccup);
pr_info("%s %d, %d\n", __func__, en, ret);
}
#endif
#if defined(CONFIG_DRV_SAMSUNG)
static ssize_t afc_disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_afc_ddata *ddata = dev_get_drvdata(dev);
if (ddata->afc_disable) {
pr_info("%s AFC DISABLE\n", __func__);
return sprintf(buf, "1\n");
}
pr_info("%s AFC ENABLE", __func__);
return sprintf(buf, "0\n");
}
static ssize_t afc_disable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct gpio_afc_ddata *ddata = dev_get_drvdata(dev);
int param_val;
#if defined(CONFIG_SEC_PARAM)
int ret = 0;
#endif
#ifdef CONFIG_BATTERY_SAMSUNG
union power_supply_propval psy_val;
#endif
#if IS_ENABLED(CONFIG_VIRTUAL_MUIC)
int attached_dev = vt_muic_get_attached_dev();
int vbus = 0, curr = 0;
#endif /* CONFIG_VIRTUAL_MUIC */
if (!strncasecmp(buf, "1", 1)) {
ddata->afc_disable = true;
} else if (!strncasecmp(buf, "0", 1)) {
ddata->afc_disable = false;
} else {
pr_warn("%s invalid value\n", __func__);
return count;
}
param_val = ddata->afc_disable ? '1' : '0';
#if defined(CONFIG_SEC_PARAM)
ret = sec_set_param(CM_OFFSET + 1, ddata->afc_disable ? '1' : '0');
if (ret < 0)
pr_err("%s:sec_set_param failed\n", __func__);
#endif
#ifdef CONFIG_BATTERY_SAMSUNG
psy_val.intval = param_val;
psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_HV_DISABLE, psy_val);
#endif
#if IS_ENABLED(CONFIG_VIRTUAL_MUIC)
switch (attached_dev) {
case ATTACHED_DEV_TA_MUIC:
case ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC ... ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC:
kthread_flush_work(&ddata->kwork);
curr = vbus_level_check();
vbus = ddata->afc_disable ? 5 : 9;
if (curr == vbus) {
if (ddata->afc_disable && attached_dev != ATTACHED_DEV_TA_MUIC)
vt_muic_set_attached_afc_dev(ATTACHED_DEV_TA_MUIC);
} else {
ddata->curr_voltage = curr;
ddata->set_voltage = vbus;
kthread_queue_work(&ddata->kworker, &ddata->kwork);
}
break;
default:
break;
}
#endif /* CONFIG_VIRTUAL_MUIC */
pr_info("%s afc_disable(%d)\n", __func__, ddata->afc_disable);
return count;
}
#if IS_ENABLED(CONFIG_SEC_HICCUP)
static ssize_t hiccup_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "ENABLE\n");
}
static ssize_t hiccup_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
if (!strncasecmp(buf, "DISABLE", 7)) {
#if defined(CONFIG_PDIC_NOTIFIER)
pd_noti.sink_status.fp_sec_pd_manual_ccopen_req(0);
#endif
set_sec_hiccup(false);
} else
pr_warn("%s invalid com : %s\n", __func__, buf);
return count;
}
#endif /* CONFIG_SEC_HICCUP */
static ssize_t adc_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int result = 0;
#if IS_ENABLED(CONFIG_TCPC_MT6360) && IS_ENABLED(CONFIG_PDIC_NOTIFIER)
//temp result = mt6360_usbid_check();
#endif
pr_info("%s %d\n", __func__, result);
return sprintf(buf, "%d\n", !!result);
}
static ssize_t vbus_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int val;
val = vbus_level_check();
pr_info("%s vbus=%d\n", __func__, val);
return sprintf(buf, "%d\n", val);
}
static DEVICE_ATTR_RO(adc);
static DEVICE_ATTR_RO(vbus_value);
static DEVICE_ATTR_RW(afc_disable);
#if IS_ENABLED(CONFIG_SEC_HICCUP)
static DEVICE_ATTR_RW(hiccup);
#endif /* CONFIG_SEC_HICCUP */
static struct attribute *gpio_afc_attributes[] = {
&dev_attr_adc.attr,
&dev_attr_vbus_value.attr,
&dev_attr_afc_disable.attr,
#if IS_ENABLED(CONFIG_SEC_HICCUP)
&dev_attr_hiccup.attr,
#endif /* CONFIG_SEC_HICCUP */
NULL
};
static const struct attribute_group gpio_afc_group = {
.attrs = gpio_afc_attributes,
};
#endif /* CONFIG_DRV_SAMSUNG */
#if IS_ENABLED(CONFIG_HICCUP_CHARGER)
static int sec_set_hiccup_mode(int val)
{
#if IS_ENABLED(CONFIG_SEC_HICCUP)
#if defined(CONFIG_PDIC_USE_MODULE_PARAM)
if (is_lpcharge_pdic_param()) {
pr_info("%s %d, lpm mode\n", __func__, val);
return 0;
}
#endif
if (val) {
set_attached_afc_dev(ATTACHED_DEV_HICCUP_MUIC);
set_sec_hiccup(true);
} else {
set_sec_hiccup(false);
}
#else
pr_info("%s gpio_hiccup is not defined\n", __func__);
#endif
return 0;
}
#endif
static struct gpio_afc_pdata *gpio_afc_get_dt(struct device *dev)
{
struct device_node *np = dev->of_node;
struct gpio_afc_pdata *pdata;
if (!np)
return ERR_PTR(-ENODEV);
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->gpio_afc_switch = of_get_named_gpio(np, "gpio_afc_switch", 0);
pdata->gpio_afc_data = of_get_named_gpio(np, "gpio_afc_data", 0);
pr_info("request gpio_afc_switch %d, gpio_afc_data %d\n",
pdata->gpio_afc_switch, pdata->gpio_afc_data);
#if IS_ENABLED(CONFIG_SEC_HICCUP)
gpio_hiccup = of_get_named_gpio(np, "gpio_hiccup_en", 0);
pr_info("gpio_hiccup_en : %d\n", gpio_hiccup);
#endif /* CONFIG_SEC_HICCUP */
return pdata;
}
static int gpio_afc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpio_afc_pdata *pdata = dev_get_platdata(dev);
struct gpio_afc_ddata *ddata;
struct task_struct *kworker_task;
#if defined(CONFIG_BATTERY_SAMSUNG)
union power_supply_propval psy_val;
#endif
int ret = 0;
pr_info("%s\n", __func__);
if (!pdata)
pdata = gpio_afc_get_dt(dev);
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
ret = gpio_request(pdata->gpio_afc_switch, "gpio_afc_switch");
if (ret < 0) {
pr_err("failed to request afc switch gpio\n");
return ret;
}
ret = gpio_request(pdata->gpio_afc_data, "gpio_afc_data");
if (ret < 0) {
pr_err("failed to request afc data gpio\n");
return ret;
}
#if IS_ENABLED(CONFIG_SEC_HICCUP)
ret = gpio_request(gpio_hiccup, "hiccup_en");
if (ret < 0) {
pr_err("failed to request hiccup gpio\n");
return ret;
}
#endif /* CONFIG_SEC_HICCUP */
ddata->is_hiccup_mode = SEC_HICCUP_MODE_OFF;
ddata->ws = wakeup_source_register(NULL, "afc_wakelock");
spin_lock_init(&ddata->spin_lock);
mutex_init(&ddata->mutex);
dev_set_drvdata(dev, ddata);
kthread_init_worker(&ddata->kworker);
kworker_task = kthread_run(kthread_worker_fn,
&ddata->kworker, "gpio_afc");
if (IS_ERR(kworker_task))
pr_err("Failed to create message pump task\n");
kthread_init_work(&ddata->kwork, gpio_afc_kwork);
ddata->pdata = pdata;
ddata->gpio_input = false;
g_ddata = ddata;
afc_mode = 0;
#if defined(CONFIG_DRV_SAMSUNG)
ddata->afc_disable = (_get_afc_mode() == '1' ? 1 : 0);
#ifdef CONFIG_BATTERY_SAMSUNG
psy_val.intval = ddata->afc_disable ? '1' : '0';
psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_HV_DISABLE, psy_val);
#endif
#if defined(CONFIG_MUIC_NOTIFIER)
if (switch_device) {
pr_info("switch_device is aleady created in muic common\n");
ddata->dev = switch_device;
dev_set_drvdata(ddata->dev, ddata);
} else
#endif
{
ddata->dev = sec_device_create(ddata, "switch");
if (IS_ERR(ddata->dev)) {
pr_err("failed to create device\n");
return -ENODEV;
}
}
ret = sysfs_create_group(&ddata->dev->kobj, &gpio_afc_group);
if (ret) {
pr_err("failed to create afc_disable sysfs\n");
return ret;
}
#endif
gpio_direction_output(ddata->pdata->gpio_afc_switch, 0);
gpio_direction_output(ddata->pdata->gpio_afc_data, 0);
#if IS_ENABLED(CONFIG_MUIC_NOTIFIER)
ddata->muic_pdata = &muic_pdata;
ddata->muic_pdata->muic_afc_set_voltage_cb = set_afc_voltage;
#endif
#if IS_ENABLED(CONFIG_HICCUP_CHARGER)
ddata->muic_pdata->muic_set_hiccup_mode_cb =
sec_set_hiccup_mode;
#endif
return 0;
}
static void gpio_afc_shutdown(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpio_afc_ddata *ddata = dev_get_drvdata(dev);
int vol;
if (!ddata) {
pr_err("%s: driver is not ready\n", __func__);
return;
}
vol = vbus_level_check();
pr_info("%s: vbus %d (set_voltage=%d)\n", __func__,
vol, ddata->set_voltage);
if (ddata->set_voltage == 0x9 && vol == 0x9) {
gpio_afc_reset(ddata);
vol = vbus_level_check();
pr_info("%s: after afc reset=> vbus %d\n", __func__, vol);
if (vol == 0x9)
gpio_afc_set_voltage(ddata, 0x5);
}
}
static const struct of_device_id gpio_afc_of_match[] = {
{.compatible = "gpio_afc",},
{},
};
MODULE_DEVICE_TABLE(of, gpio_afc_of_match);
static struct platform_driver gpio_afc_driver = {
.shutdown = gpio_afc_shutdown,
.driver = {
.name = "gpio_afc",
.of_match_table = gpio_afc_of_match,
},
};
module_platform_driver_probe(gpio_afc_driver, gpio_afc_probe);
MODULE_DESCRIPTION("Samsung GPIO AFC driver");
MODULE_LICENSE("GPL");