/*
 *  s2asl01_switching.c
 *  Samsung S2ASL01 Switching IC driver
 *
 *  Copyright (C) 2018 Samsung Electronics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/power/s2asl01_switching.h>
#include <linux/of.h>
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kernel.h>

static enum power_supply_property s2asl01_main_props[] = {
	POWER_SUPPLY_PROP_VOLTAGE_NOW,
	POWER_SUPPLY_PROP_CURRENT_NOW
};

static enum power_supply_property s2asl01_sub_props[] = {
	POWER_SUPPLY_PROP_VOLTAGE_NOW,
	POWER_SUPPLY_PROP_CURRENT_NOW
};

static struct device_attribute s2asl01_limiter_attrs[] = {
	S2ASL01_LIMITER_ATTR(limiter_data),
};

static int s2asl01_write_reg(struct i2c_client *client, int reg, u8 data)
{
	struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
	int ret = 0;

	mutex_lock(&switching->i2c_lock);
	ret = i2c_smbus_write_byte_data(client, reg, data);
	mutex_unlock(&switching->i2c_lock);
	if (ret < 0) {
		pr_info("%s [%s]: reg(0x%x), ret(%d)\n",
			__func__, current_limiter_type_str[switching->pdata->bat_type], reg, ret);
	}
	return ret;
}

static int s2asl01_read_reg(struct i2c_client *client, int reg, void *data)
{
	struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
	int ret = 0;

	mutex_lock(&switching->i2c_lock);
	ret = i2c_smbus_read_byte_data(client, reg);
	mutex_unlock(&switching->i2c_lock);
	if (ret < 0) {
		pr_info("%s [%s]: reg(0x%x), ret(%d)\n",
			__func__, current_limiter_type_str[switching->pdata->bat_type], reg, ret);
		return ret;
	}
	ret &= 0xff;
	*(u8 *)data = (u8)ret;

	return ret;
}

static int s2asl01_update_reg(struct i2c_client *client, u8 reg, u8 val, u8 mask)
{
	struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
	int ret = 0;
	u8 old_val = 0, new_val = 0;

	mutex_lock(&switching->i2c_lock);
	ret = i2c_smbus_read_byte_data(client, reg);
	if (ret >= 0) {
		old_val = ret & 0xff;
		new_val = (val & mask) | (old_val & (~mask));
		ret = i2c_smbus_write_byte_data(client, reg, new_val);
	}
	mutex_unlock(&switching->i2c_lock);
	return ret;
}

static void s2asl01_test_read(struct i2c_client *client)
{
	struct s2asl01_switching_data *switching = i2c_get_clientdata(client);
	u8 data = 0;
	char str[1016] = {0,};
	int i = 0;

	/* address 0x00 ~ 0x1f */
	for (i = 0x0; i <= 0x1F; i++) {
		s2asl01_read_reg(switching->client, i, &data);
		sprintf(str+strlen(str), "0x%02x:0x%02x, ", i, data);
	}

	pr_info("%s [%s]: %s\n", __func__, current_limiter_type_str[switching->pdata->bat_type], str);
}

static void s2asl01_set_in_ok(struct s2asl01_switching_data *switching, bool onoff)
{
	pr_info("%s[%s]: INOK = %d\n", __func__, current_limiter_type_str[switching->pdata->bat_type], onoff);
	if (onoff)
		s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
			1 << S2ASL01_CTRL3_INOK_SHIFT, S2ASL01_CTRL3_INOK_MASK);
	else
		s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
			0 << S2ASL01_CTRL3_INOK_SHIFT, S2ASL01_CTRL3_INOK_MASK);
}

static void s2asl01_set_supllement_mode(struct s2asl01_switching_data *switching, bool onoff)
{
	pr_info("%s[%s]: SUPLLEMENT MODE = %d\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], onoff);

	if (onoff) {
		s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
			1 << S2ASL01_CTRL3_SUPLLEMENTMODE_SHIFT, S2ASL01_CTRL3_SUPLLEMENTMODE_MASK);
		switching->supllement_mode = true;
	} else {
		s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
			0 << S2ASL01_CTRL3_SUPLLEMENTMODE_SHIFT, S2ASL01_CTRL3_SUPLLEMENTMODE_MASK);
		switching->supllement_mode = false;
	}
}

static void s2asl01_set_recharging_start(struct s2asl01_switching_data *switching)
{
	pr_info("%s: INOK = %d, SUPLLEMENT MODE = %d\n",
		__func__, switching->in_ok, switching->supllement_mode);

	s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL1,
			1 << S2ASL01_CTRL1_RESTART_SHIFT, S2ASL01_CTRL1_RESTART_MASK);
}

static void s2asl01_set_eoc_on(struct s2asl01_switching_data *switching)
{
	pr_info("%s[%s]: INOK = %d\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], switching->in_ok);

	s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL1,
			1 << S2ASL01_CTRL1_EOC_SHIFT, S2ASL01_CTRL1_EOC_MASK);
}

static void s2asl01_set_dischg_mode(struct s2asl01_switching_data *switching, int chg_mode)
{
	u8 val = 0;
	int ret = 0;

	switch (chg_mode) {
	case CURRENT_REGULATION:
	case CURRENT_VOLTAGE_REGULATION:
	case NO_REGULATION_FULLY_ON:
		val = chg_mode;
		break;
	case CURRENT_SMARTER_VOLTAGE_REGULATION:
		pr_info("%s: chg_mode(%d) changes to CURRENT_VOLTAGE_REGULATION\n",
			__func__, chg_mode);
		val = CURRENT_VOLTAGE_REGULATION;
		break;
	default:
		pr_info("%s: wrong input(%d)\n", __func__, chg_mode);
		break;
	}

	ret = s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
			val << S2ASL01_CTRL3_DIS_MODE_SHIFT, S2ASL01_CTRL3_DIS_MODE_MASK);
	if (ret < 0)
		pr_err("%s, i2c read fail\n", __func__);

	pr_info("%s: set discharge mode (%d)\n", __func__, chg_mode);
}

static int s2asl01_get_dischg_mode(struct s2asl01_switching_data *switching)
{
	u8 val = 0, chg_mode = 0;
	int ret = 0;

	ret = s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3, &val);
	chg_mode = (val & S2ASL01_CTRL3_DIS_MODE_MASK) >> S2ASL01_CTRL3_DIS_MODE_SHIFT;

	switch (chg_mode) {
	case CURRENT_REGULATION:
	case CURRENT_VOLTAGE_REGULATION:
	case NO_REGULATION_FULLY_ON:
		return chg_mode;
	case CURRENT_SMARTER_VOLTAGE_REGULATION:
		pr_info("%s: chg_mode(%d) CURRENT_VOLTAGE_REGULATION\n",
			__func__, chg_mode);
		return CURRENT_VOLTAGE_REGULATION;
	default:
		return -EINVAL;
	}
}

static void s2asl01_set_chg_mode(struct s2asl01_switching_data *switching, int chg_mode)
{
	u8 val = 0;
	int ret = 0;

	switch (chg_mode) {
	case CURRENT_REGULATION:
	case CURRENT_VOLTAGE_REGULATION:
	case CURRENT_SMARTER_VOLTAGE_REGULATION:
	case NO_REGULATION_FULLY_ON:
		val = chg_mode;
		break;
	default:
		pr_info("%s: wrong input(%d)\n", __func__, chg_mode);
		break;
	}

	ret = s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3,
			val << S2ASL01_CTRL3_CHG_MODE_SHIFT, S2ASL01_CTRL3_CHG_MODE_MASK);
	if (ret < 0)
		pr_err("%s, i2c read fail\n", __func__);

	pr_info("%s: set charge mode (%d)\n", __func__, chg_mode);
}

static int s2asl01_get_chg_mode(struct s2asl01_switching_data *switching)
{
	u8 val = 0, chg_mode = 0;
	int ret = 0;

	ret = s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3, &val);
	chg_mode = (val & S2ASL01_CTRL3_CHG_MODE_MASK) >> S2ASL01_CTRL3_CHG_MODE_SHIFT;

	switch (chg_mode) {
	case CURRENT_REGULATION:
	case CURRENT_VOLTAGE_REGULATION:
	case CURRENT_SMARTER_VOLTAGE_REGULATION:
	case NO_REGULATION_FULLY_ON:
		return chg_mode;
	default:
		return -EINVAL;
	}
}

static int s2asl01_get_vchg(struct s2asl01_switching_data *switching, int mode)
{
	u8 data[2] = {0, };
	u16 temp = 0;
	u32 vchg = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_VCHG, &data[0]);
	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_VCHG, &data[1]);

	//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
	//	__func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);

	temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
	temp &= 0x0FFF;
	vchg = temp * 1250;

	if (mode == S2M_BATTERY_VOLTAGE_MV)
		vchg = vchg / 1000;

	pr_info("%s [%s]: vchg = %d %s\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], vchg,
		(mode == S2M_BATTERY_VOLTAGE_UV) ? "uV" : "mV");

	return vchg;
}

static int s2asl01_get_vbat(struct s2asl01_switching_data *switching, int mode)
{
	u8 data[2] = {0, };
	u16 temp = 0;
	u32 vbat = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_VBAT, &data[0]);
	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_VBAT, &data[1]);

	//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
	//	__func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);

	temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
	temp &= 0x0FFF;
	vbat = temp * 1250;

	if (mode == S2M_BATTERY_VOLTAGE_MV)
		vbat = vbat / 1000;

	pr_info("%s [%s]: vbat = %d %s\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], vbat,
		(mode == S2M_BATTERY_VOLTAGE_UV) ? "uV" : "mV");

	return vbat;
}

static int s2asl01_get_ichg(struct s2asl01_switching_data *switching, int mode)
{
	u8 data[2] = {0, };
	u16 temp = 0;
	u32 ichg = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_ICHG, &data[0]);
	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_ICHG, &data[1]);

	//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
	//	__func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);

	temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
	temp &= 0x0FFF;
	ichg = (temp * 15625) / 10;

	if (mode == S2M_BATTERY_CURRENT_MA)
		ichg = ichg / 1000;

	pr_info("%s [%s]: Ichg = %d %s\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], ichg,
		(mode == S2M_BATTERY_CURRENT_UA) ? "uA" : "mA");

	return ichg;
}

static int s2asl01_get_idischg(struct s2asl01_switching_data *switching, int mode)
{
	u8 data[2] = {0, };
	u16 temp = 0;
	u32 idischg = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL1_IDISCHG, &data[0]);
	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_VAL2_IDISCHG, &data[1]);

	//pr_info("%s [%s]: data0 (%d) data1 (%d)\n",
	//	__func__, current_limiter_type_str[switching->pdata->bat_type], data[0], data[1]);

	temp = (data[0] << 4) | ((data[1] & 0xF0) >> 4);
	temp &= 0x0FFF;
	idischg = (temp * 15625) / 10;

	if (mode == S2M_BATTERY_CURRENT_MA)
		idischg = idischg / 1000;

	pr_info("%s [%s]: IDISCHG = %d %s\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], idischg,
		(mode == S2M_BATTERY_CURRENT_UA) ? "uA" : "mA");

	return idischg;
}

static void s2asl01_set_fast_charging_current_limit(struct s2asl01_switching_data *switching, int charging_current)
{
	u8 data = 0;

	if (switching->rev_id == 0) {
		if (charging_current <= 50)
			data = 0x00;
		else if (charging_current > 50 && charging_current <= 3200)
			data = (charging_current / 50) - 1;
		else
			data = 0x3F;
	} else {
		if (charging_current <= 50)
			data = 0x00;
		else if (charging_current > 50 && charging_current <= 350)
			data = (charging_current - 50) / 75;
		else if (charging_current > 350 && charging_current <= 3300)
			data = ((charging_current - 350) / 50) + 4;
		else
			data = 0x3F;
	}

	pr_info("%s [%s]: current %d, 0x%02x\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);

	s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL4, data, FCC_CHG_CURRENTLIMIT_MASK);
}

static int s2asl01_get_fast_charging_current_limit(struct s2asl01_switching_data *switching)
{
	u8 data = 0;
	int charging_current = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL4, &data);

	data = data & FCC_CHG_CURRENTLIMIT_MASK;

	if (data > 0x3F) {
		pr_err("%s: Invalid fast charging current limit value\n", __func__);
		data = 0x3F;
	}

	if (switching->rev_id == 0)
		charging_current = (data + 1) * 50;
	else {
		if ((data >= 0x00) && (data <= 0x04))
			charging_current = (data * 75) + 50;
		else
			charging_current = ((data - 4) * 50) + 350;
	}

	return charging_current;
}

static void s2asl01_set_trickle_charging_current_limit(struct s2asl01_switching_data *switching, int charging_current)
{
	u8 data = 0;

	if (switching->rev_id == 0) {
		if (charging_current <= 50)
			data = 0x00;
		else if (charging_current > 50 && charging_current <= 500)
			data = (charging_current / 50) - 1;
		else
			data = 0x09;
	} else {
		if (charging_current <= 50)
			data = 0x00;
		else if (charging_current > 50 && charging_current <= 350)
			data = (charging_current - 50) / 75;
		else if (charging_current > 350 && charging_current <= 550)
			data = ((charging_current - 350) / 50) + 4;
		else
			data = 0x08;
	}

	pr_info("%s [%s]: current %d, 0x%02x\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);

	s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL5, data, TRICKLE_CHG_CURRENT_LIMIT_MASK);
}

static int s2asl01_get_trickle_charging_current_limit(struct s2asl01_switching_data *switching)
{
	u8 data = 0;
	int charging_current = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL5, &data);

	data = data & TRICKLE_CHG_CURRENT_LIMIT_MASK;

	if (switching->rev_id == 0) {
		if (data > 0x09) {
			pr_err("%s: Invalid trickle charging current limit value\n", __func__);
			data = 0x09;
		}
		charging_current = (data + 1) * 50;
	} else {
		if (data > 0x08) {
			pr_err("%s: Invalid trickle charging current limit value\n", __func__);
			data = 0x08;
		}
		if ((data >= 0x00) && (data <= 0x04))
			charging_current = (data * 75) + 50;
		else
			charging_current = ((data - 4) * 50) + 350;
	}

	return charging_current;
}

static void s2asl01_set_dischg_charging_current_limit(struct s2asl01_switching_data *switching, int charging_current)
{
	u8 data = 0;

	if (switching->rev_id == 0) {
		if (charging_current <= 50)
			data = 0x00;
		else if (charging_current > 50 && charging_current <= 6400)
			data = (charging_current / 50) - 1;
		else
			data = 0x7F;
	} else {
		if (charging_current <= 50)
			data = 0x00;
		else if (charging_current > 50 && charging_current <= 350)
			data = (charging_current - 50) / 75;
		else if (charging_current > 350 && charging_current <= 6500)
			data = ((charging_current - 350) / 50) + 4;
		else
			data = 0x7F;
	}
	pr_info("%s [%s]: current %d, 0x%02x\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);

	s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL6, data, DISCHG_CURRENT_LIMIT_MASK);
}

static int s2asl01_get_dischg_charging_current_limit(struct s2asl01_switching_data *switching)
{
	u8 data = 0;
	int dischg_current = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL6, &data);

	data = data & DISCHG_CURRENT_LIMIT_MASK;

	if (data > 0x7F) {
		pr_err("%s: Invalid discharging current limit value\n", __func__);
		data = 0x7F;
	}

	if (switching->rev_id == 0)
		dischg_current = (data + 1) * 50;
	else {
		if ((data >= 0x00) && (data <= 0x04))
			dischg_current = (data * 75) + 50;
		else
			dischg_current = ((data - 4) * 50) + 350;
	}

	return dischg_current;
}

static void s2asl01_set_recharging_voltage(struct s2asl01_switching_data *switching, int charging_voltage)
{
	u8 data = 0;

	if (charging_voltage < 4000)
		data = 0x90;
	else if (charging_voltage >= 4000 && charging_voltage < 4400)
		data = (charging_voltage - 2560) / 10;
	else
		data = 0xB8;

	pr_info("%s: voltage %d, 0x%02x\n", __func__, charging_voltage, data);

	s2asl01_write_reg(switching->client, S2ASL01_SWITCHING_TOP_RECHG_CTRL1, data);
}

static int s2asl01_get_recharging_voltage(struct s2asl01_switching_data *switching)
{
	u8 data = 0;
	int charging_voltage = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_TOP_RECHG_CTRL1, &data);
	charging_voltage = (data * 10) + 2560;

	return charging_voltage;
}

static void s2asl01_set_eoc_voltage(struct s2asl01_switching_data *switching, int charging_voltage)
{
	u8 data = 0;

	if (charging_voltage < 4000)
		data = 0x90;
	else if (charging_voltage >= 4000 && charging_voltage < 4400)
		data = (charging_voltage - 2560) / 10;
	else
		data = 0xB8;

	pr_info("%s [%s]: voltage %d, 0x%02x\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], charging_voltage, data);

	s2asl01_write_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL1, data);
}

static int s2asl01_get_eoc_voltage(struct s2asl01_switching_data *switching)
{
	u8 data = 0;
	int charging_voltage = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL1, &data);
	charging_voltage = (data * 10) + 2560;

	return charging_voltage;
}

static void s2asl01_set_eoc_current(struct s2asl01_switching_data *switching, int charging_current)
{
	u8 data = 0;

	if (charging_current < 100)
		data = 0x04;
	else if (charging_current >= 100 && charging_current < 775)
		data = charging_current / 25;
	else
		data = 0x1F;

	pr_info("%s [%s]: current %d, 0x%02x\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type], charging_current, data);

	s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL2, data, 0x1F);
}

static int s2asl01_get_eoc_current(struct s2asl01_switching_data *switching)
{
	u8 data = 0;
	int charging_current = 0;

	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_TOP_EOC_CTRL2, &data);
	data &= 0x1F;
	charging_current = data * 25;

	return charging_current;
}

static void s2asl01_powermeter_onoff(struct s2asl01_switching_data *switching, bool onoff)
{
	pr_info("%s [%s]: (%d)\n", __func__, current_limiter_type_str[switching->pdata->bat_type], onoff);

	/* Power Meter Continuous Operation Mode
	 * [7]: VCHG
	 * [6]: VBAT
	 * [5]: ICHG
	 * [4]: IDISCHG
	 */
	if (onoff) {
		s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_PM_ENABLE, 0xF0, 0xF0);
		switching->power_meter = true;
	} else {
		s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_PM_ENABLE, 0x00, 0xF0);
		switching->power_meter = false;
	}
}

static int s2asl01_get_property(struct power_supply *psy,
				enum power_supply_property psp,
				union power_supply_propval *val)
{
	struct s2asl01_switching_data *switching = power_supply_get_drvdata(psy);
	enum s2m_power_supply_property s2m_psp = (enum s2m_power_supply_property)psp;
	int vchg, vbat, ichg, idischg;

	//pr_info("%s [%s]\n", __func__, current_limiter_type_str[switching->pdata->bat_type]);

	switch ((int)psp) {
	case POWER_SUPPLY_PROP_HEALTH:
		vchg = s2asl01_get_vchg(switching, S2M_BATTERY_VOLTAGE_MV);
		vbat = s2asl01_get_vbat(switching, S2M_BATTERY_VOLTAGE_MV);
		ichg = s2asl01_get_ichg(switching, S2M_BATTERY_CURRENT_MA);
		idischg = s2asl01_get_idischg(switching, S2M_BATTERY_CURRENT_MA);
		pr_info("%s [%s]: vchg=%dmV, vbat=%dmV, ichg=%dmA, idischg=%dmA\n",
			__func__, current_limiter_type_str[switching->pdata->bat_type],
			vchg, vbat, ichg, idischg);
		break;
	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
		val->intval = s2asl01_get_vbat(switching, val->intval);
		break;
	case POWER_SUPPLY_PROP_CURRENT_NOW:
		val->intval = s2asl01_get_ichg(switching, S2M_BATTERY_CURRENT_UA);
		if (val->intval == 0)
			val->intval = s2asl01_get_idischg(switching, S2M_BATTERY_CURRENT_UA) * (-1);
		break;
	case POWER_SUPPLY_S2M_PROP_MIN ... POWER_SUPPLY_S2M_PROP_MAX:
		switch (s2m_psp) {
		case POWER_SUPPLY_S2M_PROP_CHGIN_OK:
			val->intval = switching->in_ok ? 1 : 0;
			break;
		case POWER_SUPPLY_S2M_PROP_SUPLLEMENT_MODE:
			val->intval = switching->supllement_mode ? 1 : 0;
			break;
		case POWER_SUPPLY_S2M_PROP_DISCHG_MODE:
			val->intval = s2asl01_get_dischg_mode(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_CHG_MODE:
			val->intval = s2asl01_get_chg_mode(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_CHG_VOLTAGE:
			val->intval = s2asl01_get_vchg(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_BAT_VOLTAGE:
			val->intval = s2asl01_get_vbat(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_CHG_CURRENT:
			val->intval = s2asl01_get_ichg(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_DISCHG_CURRENT:
			val->intval = s2asl01_get_idischg(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_FASTCHG_LIMIT_CURRENT:
			val->intval = s2asl01_get_fast_charging_current_limit(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_TRICKLECHG_LIMIT_CURRENT:
			val->intval = s2asl01_get_trickle_charging_current_limit(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_DISCHG_LIMIT_CURRENT:
			val->intval = s2asl01_get_dischg_charging_current_limit(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_RECHG_VOLTAGE:
			val->intval = s2asl01_get_recharging_voltage(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_EOC_VOLTAGE:
			val->intval = s2asl01_get_eoc_voltage(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_EOC_CURRENT:
			val->intval = s2asl01_get_eoc_current(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_POWERMETER_ENABLE:
			val->intval = switching->power_meter;
			break;
		default:
			return -EINVAL;
		}
		break;
	default:
		return -EINVAL;
	}

	//s2asl01_test_read(switching->client);
	return 0;
}

static int s2asl01_set_property(struct power_supply *psy,
				enum power_supply_property psp,
				const union power_supply_propval *val)
{
	struct s2asl01_switching_data *switching = power_supply_get_drvdata(psy);
	enum s2m_power_supply_property s2m_psp = (enum s2m_power_supply_property)psp;

	//pr_info("%s [%s]\n", __func__, current_limiter_type_str[switching->pdata->bat_type]);

	switch ((int)psp) {
	case POWER_SUPPLY_PROP_CHARGE_FULL:
		pr_info("%s [%s]: is it full? %d\n",
			__func__, current_limiter_type_str[switching->pdata->bat_type], val->intval);
		s2asl01_set_supllement_mode(switching, val->intval);
		break;
	case POWER_SUPPLY_PROP_ENERGY_NOW:
		pr_info("%s [%s]: pwr off mode 2 %d\n",
			__func__, current_limiter_type_str[switching->pdata->bat_type], val->intval);
		if (val->intval)
			s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL2,
					S2ASL01_SUB_PWR_OFF_MODE2, S2ASL01_SUB_PWR_OFF_MODE_MASK);
		else
			s2asl01_update_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL2,
					S2ASL01_SUB_PWR_OFF_MODE1, S2ASL01_SUB_PWR_OFF_MODE_MASK);
		break;
	case POWER_SUPPLY_S2M_PROP_MIN ... POWER_SUPPLY_S2M_PROP_MAX:
		switch (s2m_psp) {
		case POWER_SUPPLY_S2M_PROP_CHARGING_ENABLED:
			pr_info("%s [%s]: chg en ? %d\n",
					__func__, current_limiter_type_str[switching->pdata->bat_type], val->intval);
			//switching->in_ok = val->intval;
			//s2asl01_set_in_ok(switching, switching->in_ok);
			//s2asl01_set_eoc_on(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_CHGIN_OK:
			switching->in_ok = val->intval;
			s2asl01_set_in_ok(switching, switching->in_ok);
			break;
		case POWER_SUPPLY_S2M_PROP_SUPLLEMENT_MODE:
			s2asl01_set_supllement_mode(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_RECHG_ON:
			s2asl01_set_recharging_start(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_EOC_ON:
			s2asl01_set_eoc_on(switching);
			break;
		case POWER_SUPPLY_S2M_PROP_DISCHG_MODE:
			s2asl01_set_dischg_mode(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_CHG_MODE:
			s2asl01_set_chg_mode(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_FASTCHG_LIMIT_CURRENT:
			s2asl01_set_fast_charging_current_limit(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_TRICKLECHG_LIMIT_CURRENT:
			s2asl01_set_trickle_charging_current_limit(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_DISCHG_LIMIT_CURRENT:
			s2asl01_set_dischg_charging_current_limit(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_RECHG_VOLTAGE:
			s2asl01_set_recharging_voltage(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_EOC_VOLTAGE:
			s2asl01_set_eoc_voltage(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_EOC_CURRENT:
			s2asl01_set_eoc_current(switching, val->intval);
			break;
		case POWER_SUPPLY_S2M_PROP_POWERMETER_ENABLE:
			s2asl01_powermeter_onoff(switching, val->intval);
			break;
		default:
			return -EINVAL;
		}
		break;
	default:
		return -EINVAL;
	}

	s2asl01_test_read(switching->client);
	return 0;
}

#if IS_ENABLED(CONFIG_OF)
static int s2asl01_switching_parse_dt(struct device *dev, struct s2asl01_platform_data *pdata)
{
	//struct device_node *np = of_find_node_by_name(NULL, "s2asl01-switching");
	struct device_node *np = dev->of_node;
	int ret = 0;
	//enum of_gpio_flags irq_gpio_flags;

	pr_info("%s parsing start\n", __func__);

	if (np == NULL) {
		pr_err("%s np is NULL\n", __func__);
	} else {
		ret = of_property_read_u32(np, "limiter,bat_type", &pdata->bat_type);
		if (ret < 0)
			pr_info("%s: bat_type is empty\n", __func__);

		if (pdata->bat_type & LIMITER_MAIN) {
			pr_info("%s: It is MAIN battery dt\n", __func__);
#if 0
			ret = pdata->bat_enb = of_get_named_gpio_flags(np, "limiter,main_bat_enb_gpio",
									0, &irq_gpio_flags);
			if (ret < 0)
				dev_err(dev, "%s: can't main_bat_enb_gpio\r\n", __func__);
#endif
			np = of_find_node_by_name(NULL, "sec-dual-battery");
			if (!np)
				pr_info("%s: np NULL\n", __func__);
			else {
				ret = of_property_read_u32(np, "battery,main_charging_rate", &pdata->charging_rate);
				if (ret)
					pdata->charging_rate = 60;
				pr_info("%s: pdata->charging_rate(%d)\n", __func__, pdata->charging_rate);
			}
			np = of_find_node_by_name(NULL, "s2asl01-switching-main");
		} else if (pdata->bat_type & LIMITER_SUB) {
			pr_info("%s: It is SUB battery dt\n", __func__);
#if 0
			ret = pdata->bat_enb = of_get_named_gpio_flags(np, "limiter,sub_bat_enb_gpio",
									0, &irq_gpio_flags);
			if (ret < 0)
				dev_err(dev, "%s: can't sub_bat_enb_gpio\r\n", __func__);
#endif
			np = of_find_node_by_name(NULL, "sec-dual-battery");
			if (!np)
				pr_info("%s: np NULL\n", __func__);
			else {
				ret = of_property_read_u32(np, "battery,sub_charging_rate", &pdata->charging_rate);
				if (ret)
					pdata->charging_rate = 50;
				pr_info("%s: charging_rate(%d)\n", __func__, pdata->charging_rate);
			}
			np = of_find_node_by_name(NULL, "s2asl01-switching-sub");
		}

		ret = of_property_read_string(np, "limiter,switching_name", (char const **)&pdata->switching_name);
		if (ret < 0) {
			pr_info("%s: Switching IC name is empty\n", __func__);
			pdata->switching_name = "s2asl01-switching";
		}

		ret = of_property_read_u32(np, "limiter,chg_current_limit", &pdata->chg_current_limit);
		if (ret < 0) {
			ret = of_property_read_u32(np, "limiter,chg_current_max", &pdata->chg_current_max);
			if (ret < 0)
				pdata->chg_current_limit = 1650;
			else
				pdata->chg_current_limit = pdata->chg_current_max * pdata->charging_rate / 100;
		}
		pr_info("%s: Chg current limit is (%d)\n", __func__, pdata->chg_current_limit);

		ret = of_property_read_u32(np, "limiter,eoc", &pdata->eoc);
		if (ret < 0) {
			pr_info("%s: eoc is empty\n", __func__);
			pdata->eoc = 200; /* for interrupt setting, not used */
		}

		ret = of_property_read_u32(np, "limiter,float_voltage", &pdata->float_voltage);
		if (ret < 0) {
			pr_info("%s: float voltage is empty\n", __func__);
			pdata->float_voltage = 4350; /* for interrupt setting, not used */
		}

		ret = of_property_read_u32(np, "limiter,hys_vchg_level", &pdata->hys_vchg_level);
		if (ret < 0) {
			pr_info("%s: Hysteresis level is empty(vchg)\n", __func__);
			pdata->hys_vchg_level = 4; /* 250mV(default) */
		}

		ret = of_property_read_u32(np, "limiter,hys_vbat_level", &pdata->hys_vbat_level);
		if (ret < 0) {
			pr_info("%s: Hysteresis level is empty(vbat)\n", __func__);
			pdata->hys_vbat_level = 4; /* 250mV(default) */
		}

		ret = of_property_read_u32(np, "limiter,hys_ichg_level", &pdata->hys_ichg_level);
		if (ret < 0) {
			pr_info("%s: Hysteresis level is empty(ichg)\n", __func__);
			pdata->hys_ichg_level = 4; /* 500mA(default) */
		}

		ret = of_property_read_u32(np, "limiter,hys_idischg_level", &pdata->hys_idischg_level);
		if (ret < 0) {
			pr_info("%s: Hysteresis level is empty(idischg)\n", __func__);
			pdata->hys_idischg_level = 4; /* 500mA(default) */
		}

		pdata->tsd_en = (of_find_property(np, "limiter,tsd-en", NULL)) ? true : false;
	}

	pr_info("%s parsing end\n", __func__);
	return 0;
}

static const struct of_device_id s2asl01_switching_match_table[] = {
		{ .compatible = "samsung,s2asl01-switching",},
		{},
};
#else
static int s2asl01_switching_parse_dt(struct device *dev, struct s2asl01_switching_data *switching)
{
	return 0;
}

#define s2asl01_switching_match_table NULL
#endif /* CONFIG_OF */

static int s2asl01_limiter_create_attrs(struct device *dev)
{
	int i, rc;

	for (i = 0; i < (int)ARRAY_SIZE(s2asl01_limiter_attrs); i++) {
		rc = device_create_file(dev, &s2asl01_limiter_attrs[i]);
		if (rc)
			goto create_attrs_failed;
	}
	return rc;

create_attrs_failed:
	dev_err(dev, "%s: failed (%d)\n", __func__, rc);
	while (i--)
		device_remove_file(dev, &s2asl01_limiter_attrs[i]);
	return rc;
}

ssize_t s2asl01_limiter_show_attrs(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct power_supply *psy = dev_get_drvdata(dev);
	struct s2asl01_switching_data *limiter = power_supply_get_drvdata(psy);
	const ptrdiff_t offset = attr - s2asl01_limiter_attrs;
	int i = 0, j = 0;
	u8 data = 0;

	dev_info(limiter->dev, "%s: (%ld)\n", __func__, offset);

	switch (offset) {
	case LIMITER_DATA:
		for (j = 0; j <= S2ASL01_SWITCHING_PM_I_OPTION; j++) {
			s2asl01_read_reg(limiter->client, j, &data);
			i += scnprintf(buf + i, PAGE_SIZE - i,
					"0x%02x:\t0x%02x\n", j, data);
		}

		break;
	default:
		return -EINVAL;
	}
	return i;
}

ssize_t s2asl01_limiter_store_attrs(struct device *dev,	struct device_attribute *attr, const char *buf, size_t count)
{
	struct power_supply *psy = dev_get_drvdata(dev);
	struct s2asl01_switching_data *limiter = power_supply_get_drvdata(psy);
	const ptrdiff_t offset = attr - s2asl01_limiter_attrs;
	int ret = 0;
	int x, y;

	dev_info(limiter->dev, "%s: (%ld)\n", __func__, offset);

	switch (offset) {
	case LIMITER_DATA:
		if (sscanf(buf, "0x%8x 0x%8x", &x, &y) == 2) {
			u8 addr = x;
			u8 data = y;

			if (s2asl01_write_reg(limiter->client, addr, data) < 0)
				dev_info(limiter->dev, "%s: addr: 0x%x write fail\n", __func__, addr);
		}
		ret = count;
		break;
	default:
		ret = -EINVAL;
	}
	return ret;
}

static void s2asl01_get_rev_id(struct s2asl01_switching_data *switching)
{
	u8 val1 = 0, val2 = 0;

	/* rev ID */
	s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_ID, &val1);
	if ((val1 & 0xF0) == 0) {
		s2asl01_read_reg(switching->client, S2ASL01_SWITCHING_CORE_CTRL3, &val2);
		if (val2 != 0x0C) {
			/* EVT0 0x11 address default value: 0x02 */
			switching->es_num = 0;
			switching->rev_id = 0;
		} else {
			/* EVT1 0x11 address default value: 0x0C */
			switching->es_num = 1;
			switching->rev_id = 1;
		}
	} else {
		switching->es_num = (val1 & 0xC0) >> 6;
		switching->rev_id = (val1 & 0x30) >> 4;
	}

	pr_info("%s [%s]: rev id: %d, es_num = %d\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type],
		switching->rev_id, switching->es_num);
}

static void s2asl01_init_regs(struct s2asl01_switching_data *switching)
{
	pr_info("%s: s2asl01 switching initialize\n", __func__);

	/* Set supllement mode */
	s2asl01_set_supllement_mode(switching, true);

	s2asl01_test_read(switching->client);
}

static const struct power_supply_desc s2asl01_main_power_supply_desc = {
	.name = "s2asl01-switching-main",
	.type = POWER_SUPPLY_TYPE_UNKNOWN,
	.properties = s2asl01_main_props,
	.num_properties = ARRAY_SIZE(s2asl01_main_props),
	.get_property = s2asl01_get_property,
	.set_property = s2asl01_set_property,
	.no_thermal = true,
};

static const struct power_supply_desc s2asl01_sub_power_supply_desc = {
	.name = "s2asl01-switching-sub",
	.type = POWER_SUPPLY_TYPE_UNKNOWN,
	.properties = s2asl01_sub_props,
	.num_properties = ARRAY_SIZE(s2asl01_sub_props),
	.get_property = s2asl01_get_property,
	.set_property = s2asl01_set_property,
	.no_thermal = true,
};

static int s2asl01_switching_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
	struct device_node *of_node = client->dev.of_node;
	struct s2asl01_switching_data *switching;
	struct s2asl01_platform_data *pdata = client->dev.platform_data;
	struct power_supply_config psy_cfg = {};
	int ret = 0;

	dev_info(&client->dev, "%s: S2ASL01 Switching Driver Loading\n", __func__);

	if (of_node) {
		pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
		if (!pdata)
			return -ENOMEM;
		ret = s2asl01_switching_parse_dt(&client->dev, pdata);
		if (ret < 0)
			goto err_parse_dt;
	} else {
		pdata = client->dev.platform_data;
	}

	switching = kzalloc(sizeof(*switching), GFP_KERNEL);
	if (switching == NULL) {
		ret = -ENOMEM;
		goto err_limiter_nomem;
	}
	switching->dev = &client->dev;

	ret = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
							I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK);
	if (!ret) {
		ret = i2c_get_functionality(client->adapter);
		dev_err(switching->dev, "I2C functionality is not supported.\n");
		ret = -EINVAL;
		goto err_i2cfunc_not_support;
	}
	switching->client = client;
	switching->pdata = pdata;

	i2c_set_clientdata(client, switching);
	mutex_init(&switching->i2c_lock);

	if (switching->pdata->bat_type & LIMITER_MAIN) {
		psy_cfg.drv_data = switching;
		switching->psy_sw = power_supply_register(switching->dev, &s2asl01_main_power_supply_desc, &psy_cfg);
		if ((void *)switching->psy_sw < 0) {
			pr_err("%s: Failed to Register psy_sw\n", __func__);
			goto err_supply_unreg;
		}
	} else if (switching->pdata->bat_type & LIMITER_SUB) {
		psy_cfg.drv_data = switching;
		switching->psy_sw = power_supply_register(switching->dev, &s2asl01_sub_power_supply_desc, &psy_cfg);
		if ((void *)switching->psy_sw < 0) {
			pr_err("%s: Failed to Register psy_sw\n", __func__);
			goto err_supply_unreg;
		}
	}

	switching->wqueue = create_singlethread_workqueue("limiter_workqueue");
	if (!switching->wqueue) {
		pr_err("%s: Fail to Create Workqueue\n", __func__);
		goto err_pdata_free;
	}

	switching->in_ok = false;
	switching->supllement_mode = false;
	switching->power_meter = false;
#if 0
	pr_info("%s [%s]: enb = %d\n",
		__func__, current_limiter_type_str[switching->pdata->bat_type],
		gpio_get_value(switching->pdata->bat_enb));
#endif
	s2asl01_get_rev_id(switching);
	s2asl01_init_regs(switching);

	/* sysfs create */
	ret = s2asl01_limiter_create_attrs(&switching->psy_sw->dev);
	if (ret) {
		dev_err(switching->dev,
			"%s: Failed to create_attrs\n", __func__);
		goto err_irq;
	}

	dev_info(&client->dev, "%s: S2ASL01 Switching Driver Loaded\n", __func__);
	return 0;

err_irq:
err_pdata_free:
	power_supply_unregister(switching->psy_sw);
err_supply_unreg:
	mutex_destroy(&switching->i2c_lock);
err_i2cfunc_not_support:
	kfree(switching);
err_parse_dt:
err_limiter_nomem:
	devm_kfree(&client->dev, pdata);

	return ret;
}

static const struct i2c_device_id s2asl01_switching_id[] = {
	{"s2asl01-switching", 0},
	{}
};

static void s2asl01_switching_shutdown(struct i2c_client *client)
{
	pr_info("%s\n", __func__);
}

static int s2asl01_switching_remove(struct i2c_client *client)
{
	struct s2asl01_switching_data *switching = i2c_get_clientdata(client);

	power_supply_unregister(switching->psy_sw);
	mutex_destroy(&switching->i2c_lock);
	return 0;
}

#if IS_ENABLED(CONFIG_PM)
static int s2asl01_switching_suspend(struct device *dev)
{
	pr_info("%s\n", __func__);
	return 0;
}

static int s2asl01_switching_resume(struct device *dev)
{
	pr_info("%s\n", __func__);
	return 0;
}
#else
#define s2asl01_switching_suspend NULL
#define s2asl01_switching_resume NULL
#endif

static SIMPLE_DEV_PM_OPS(s2asl01_switching_pm_ops, s2asl01_switching_suspend, s2asl01_switching_resume);

static struct i2c_driver s2asl01_switching_driver = {
	.driver = {
		.name = "s2asl01-switching",
		.owner = THIS_MODULE,
		.pm = &s2asl01_switching_pm_ops,
		.of_match_table = s2asl01_switching_match_table,
	},
	.probe  = s2asl01_switching_probe,
	.remove = s2asl01_switching_remove,
	.shutdown   = s2asl01_switching_shutdown,
	.id_table   = s2asl01_switching_id,
};

static int __init s2asl01_switching_init(void)
{
	pr_info("%s: S2ASL01 Switching Init\n", __func__);
	return i2c_add_driver(&s2asl01_switching_driver);
}

static void __exit s2asl01_switching_exit(void)
{
	i2c_del_driver(&s2asl01_switching_driver);
}
module_init(s2asl01_switching_init);
module_exit(s2asl01_switching_exit);

MODULE_DESCRIPTION("Samsung S2ASL01 Switching IC Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");