diff options
author | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
---|---|---|
committer | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
commit | 871480933a1c28f8a9fed4c4d34d06c439a7a422 (patch) | |
tree | 8718f573808810c2a1e8cb8fb6ac469093ca2784 /drivers/power/wmt_battery/charger | |
parent | 9d40ac5867b9aefe0722bc1f110b965ff294d30d (diff) | |
download | FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.gz FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.bz2 FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.zip |
Moved, renamed, and deleted files
The original directory structure was scattered and unorganized.
Changes are basically to make it look like kernel structure.
Diffstat (limited to 'drivers/power/wmt_battery/charger')
6 files changed, 1213 insertions, 0 deletions
diff --git a/drivers/power/wmt_battery/charger/g2214/Kconfig b/drivers/power/wmt_battery/charger/g2214/Kconfig new file mode 100755 index 00000000..1ca457f8 --- /dev/null +++ b/drivers/power/wmt_battery/charger/g2214/Kconfig @@ -0,0 +1,5 @@ +config G2214_CHARGER
+ tristate "GMT G2214 Charger driver (GMT & others)"
+ depends on I2C
+ help
+ Say Y here to enable support for charger with g2214 chip.
\ No newline at end of file diff --git a/drivers/power/wmt_battery/charger/g2214/Makefile b/drivers/power/wmt_battery/charger/g2214/Makefile new file mode 100755 index 00000000..188872a7 --- /dev/null +++ b/drivers/power/wmt_battery/charger/g2214/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for GMT charger core. +# + +obj-y += g2214_charger.o + + diff --git a/drivers/power/wmt_battery/charger/g2214/g2214_charger.c b/drivers/power/wmt_battery/charger/g2214/g2214_charger.c new file mode 100755 index 00000000..944790e8 --- /dev/null +++ b/drivers/power/wmt_battery/charger/g2214/g2214_charger.c @@ -0,0 +1,762 @@ +/* + * g2214_charger.c - WonderMedia Charger Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <asm/uaccess.h> +#include <mach/hardware.h> +#include <mach/wmt_env.h> +#include <linux/power/wmt_battery.h> +#include <mach/gmt-core.h> +#include <linux/reboot.h> + +#define DRVNAME "gmt-charger" + +#undef pr_err +#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) + +#define REG_A0 0x00 +#define REG_A1 0x01 +#define REG_A2 0x02 +#define REG_A3 0x03 +#define REG_A4 0x04 +#define REG_A5 0x05 +#define REG_A6 0x06 +#define REG_A7 0x07 +#define REG_A8 0x08 +#define REG_A9 0x09 +#define REG_A10 0x0A +#define REG_A11 0x0B +#define REG_A12 0x0C +#define REG_A13 0x0D + +struct g2214_charger { + struct gmt2214_dev *gmt_dev; + struct device *dev; + struct mutex lock; + + struct power_supply psy_ac; + struct power_supply psy_usb; + struct delayed_work monitor_work; + + int ac_online; + int usb_online; + int charger_status; + int sleeping; + + union { + struct { + unsigned int cable_type:4; + unsigned int current_sw_mode:1; + unsigned int pc_charging:1; + }; + uint32_t flag; + }; + + int iset_dcin; + int iset_vbus; + int vseta; + int iseta_small; + int iseta_large; + int safety_time; + int otg_power; + int pc_power_mode; +}; + +static struct g2214_charger *g_charger = NULL; + +static int parse_charger_param(struct g2214_charger *ch) +{ + static const char uboot_env[] = "wmt.charger.param"; + char buf[64]; + size_t l = sizeof(buf); + int n; + + if (wmt_getsyspara((char *)uboot_env, buf, &l)) + return -ENODEV; + if (prefixcmp(buf, "g2214:")) + return -ENODEV; + if (!ch) + return 0; + + n = sscanf(buf + 6, "%x:%d:%d:%d:%d:%d:%d:%d:%d", + &ch->flag, + &ch->iset_dcin, &ch->iset_vbus, &ch->vseta, + &ch->iseta_small, &ch->iseta_large, + &ch->safety_time, &ch->otg_power, &ch->pc_power_mode); + if (n < 8) { + pr_err("%s invalid\n", uboot_env); + return -EINVAL; + } + + pr_info("charger match g2214, %s cable, %s current switch\n" + "PC connected is %scharging\n" + "dcin %d mA, vbus %d mA, %d mV\n" + "charging current %d~%d mA %d hour, %s otg power\n" + "pc power in %s mode\n", + (ch->cable_type == CABLE_TYPE_DC) ? "DC" : "USB", + (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) ? "dynamic" : "sleep", + (ch->pc_charging == PC_CONNECTED_NOT_CHARGING) ? "not " : "", + ch->iset_dcin, ch->iset_vbus, ch->vseta, + ch->iseta_small, ch->iseta_large, + ch->safety_time, ch->otg_power ? "switch" : "no", + ch->pc_power_mode ? "lowpower" : "normal"); + return 0; +} + +static int g2214_read(struct g2214_charger *ch, uint8_t reg) +{ + unsigned int rt_value = 0; + gmt2214_reg_read(ch->gmt_dev, reg, &rt_value); + return rt_value; +} + +static int g2214_write(struct g2214_charger *ch, uint8_t reg, uint8_t val) +{ + return gmt2214_reg_write(ch->gmt_dev, reg, val); +} + +static inline void g2214_enotg_config(struct g2214_charger *ch, int enable) +{ + int val = g2214_read(ch, REG_A8); + if (enable) + val |= BIT3; + else + val &= ~BIT3; + g2214_write(ch, REG_A8, val); +} + +static inline void g2214_vseta_config(struct g2214_charger *ch) +{ + int val, vseta; + + if (ch->vseta < 4150) + vseta = 0; + else if (ch->vseta < 4200) + vseta = 1; + else if (ch->vseta < 4350) + vseta = 2; + else + vseta = 3; + + val = g2214_read(ch, REG_A8); + val &= ~(3 << 6); + val |= vseta << 6; + g2214_write(ch, REG_A8, val); +} + +static inline void g2214_current_config(struct g2214_charger *ch, + int dcin_mA, int vbus_mA, int charge_mA) +{ + int iset_dcin, iset_vbus, iseta; + + if (dcin_mA <= 1000) + iset_dcin = 0; + else if (dcin_mA <= 1500) + iset_dcin = 1; + else if (dcin_mA <= 2000) + iset_dcin = 2; + else + iset_dcin = 3; + + if (vbus_mA <= 95) + iset_vbus = 0; + else if (vbus_mA <= 475) + iset_vbus = 1; + else if (vbus_mA <= 950) + iset_vbus = 2; + else + iset_vbus = 3; + + if (charge_mA < 300 || charge_mA > 1800) + iseta = 2; + else + iseta = ((charge_mA - 300) / 100); + + g2214_write(ch, REG_A5, iset_dcin << 6 | iset_vbus << 4 | iseta); +} + +static void current_refresh(struct g2214_charger *ch) +{ + int dcin_mA, vbus_mA, charge_mA; + + dcin_mA = ch->iset_dcin; + + switch (ch->charger_status) { + case POWER_SUPPLY_STATUS_DISCHARGING: + vbus_mA = ch->pc_power_mode ? 95 : 475; + charge_mA = ch->iseta_small; + break; + case POWER_SUPPLY_STATUS_FULL: + case POWER_SUPPLY_STATUS_CHARGING: + vbus_mA = ch->iset_vbus; + if (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) { + charge_mA = ch->iseta_large; + } else { + charge_mA = ch->sleeping ? ch->iseta_large + : ch->iseta_small; + } + if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) { + vbus_mA = 475; + charge_mA = ch->iseta_small; + } + break; + default: + return; + } + + printk(KERN_DEBUG " ## %s: dcin_mA %d, vbus_mA %d, charge_mA %d\n", + __func__, dcin_mA, vbus_mA, charge_mA); + g2214_current_config(ch, dcin_mA, vbus_mA, charge_mA); + g2214_vseta_config(ch); +} + +static void g2214_endpm_config(struct g2214_charger *ch, int en) +{ + int val = g2214_read(ch, REG_A0); + if (en) + val |= BIT3; + else + val &= ~BIT3; + g2214_write(ch, REG_A0, val); +} + +static void g2214_safety_time_init(struct g2214_charger *ch) +{ + int val; + int safety_time = ch->safety_time - 1; + + if (safety_time < 0) + safety_time = 0; + else if (safety_time > 16) + safety_time = 15; + + val = g2214_read(ch, REG_A6); + val &= (~(BIT4 | BIT5 | BIT6 | BIT7)); + val |= (safety_time << 4); + g2214_write(ch, REG_A6, val); +} + +static void g2214_ntc_init(struct g2214_charger *ch) +{ + int val; + val = g2214_read(ch, REG_A0); + val &= ~BIT1; //Enable Auto NTC-R Type Detection + g2214_write(ch, REG_A0, val); + g2214_write(ch, REG_A7, 0); //Set HOT boundary to 60 +} + +static int g2214_reg_init(struct g2214_charger *ch) +{ + g2214_safety_time_init(ch); + g2214_enotg_config(ch, 0); + g2214_current_config(ch, ch->iset_dcin, ch->iset_vbus, ch->iseta_small); + g2214_endpm_config(ch, ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC); + g2214_ntc_init(ch); + return 0; +} + +static void g2214_regs_dump(struct g2214_charger *ch) +{ + int reg; + for (reg = REG_A0; reg <= REG_A13; reg++) + printk(KERN_DEBUG " ## reg A%d: 0x%x\n ", + reg, g2214_read(ch, reg)); +} + +static void g2214_charge_enable(struct g2214_charger *ch, int en) +{ + int val = g2214_read(ch, REG_A6); + if (en) + val &= ~BIT3; + else + val |= BIT3; + g2214_write(ch, REG_A6, val); +} + +static inline int g2214_is_full(struct g2214_charger *ch) +{ + return !!(g2214_read(ch, REG_A12) & 0x10); +} + +static int g2214_read_status(struct g2214_charger *ch, int *pst) +{ + if (!wmt_is_dc_plugin()) { + *pst = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + switch (ch->cable_type) { + case CABLE_TYPE_DC: + *pst = POWER_SUPPLY_STATUS_CHARGING; + break; + case CABLE_TYPE_USB: + if (wmt_is_otg_plugin()) { + *pst = POWER_SUPPLY_STATUS_DISCHARGING; + } else if (wmt_is_pc_connected()) { + if (ch->pc_charging == PC_CONNECTED_CHARGING) + *pst = POWER_SUPPLY_STATUS_CHARGING; + else + *pst = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + *pst = POWER_SUPPLY_STATUS_CHARGING; + } + break; + default: + return -EINVAL; + } + + if (*pst == POWER_SUPPLY_STATUS_CHARGING) { + if (g2214_is_full(ch)) + *pst = POWER_SUPPLY_STATUS_FULL; + } + return 0; +} + +static void charger_monitor_work(struct work_struct *work) +{ + struct g2214_charger *ch= + container_of(work, struct g2214_charger, monitor_work.work); + int ac_online = 0; + int usb_online = 0; + int charger_status; + int ts_meter = 0; + + g2214_regs_dump(ch); + + if (wmt_is_otg_plugin() && ch->otg_power) + g2214_enotg_config(ch, 1); + else { + g2214_enotg_config(ch, 0); + msleep(30); + } + + g2214_write(ch, REG_A9, 0xFF); + + g2214_read_status(ch, &charger_status); + + ts_meter = (g2214_read(ch, REG_A10) & 0xE0) >> 5; + if (ts_meter == 0x11) { + charger_status = POWER_SUPPLY_STATUS_DISCHARGING; + g2214_charge_enable(ch, 0); + printk("Battery Overheat, Charge Disable\n"); + } else if (ts_meter <= 0) + g2214_charge_enable(ch, 1); + + if (charger_status == POWER_SUPPLY_STATUS_CHARGING || + charger_status == POWER_SUPPLY_STATUS_FULL) { + if (ch->cable_type == CABLE_TYPE_USB && + ch->pc_charging == PC_CONNECTED_CHARGING && + wmt_is_pc_connected()) + usb_online = 1; + else + ac_online = 1; + } + + if (ch->charger_status != charger_status || + ch->ac_online != ac_online) { + ch->charger_status = charger_status; + ch->ac_online = ac_online; + power_supply_changed(&ch->psy_ac); + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING && + ch->usb_online != usb_online) { + ch->usb_online = usb_online; + power_supply_changed(&ch->psy_usb); + } + + current_refresh(ch); + led_power_enable(charger_status == POWER_SUPPLY_STATUS_CHARGING || + charger_status == POWER_SUPPLY_STATUS_FULL); + + g2214_write(ch, REG_A9, 0xF0); + g2214_write(ch, REG_A12, g2214_read(ch, REG_A12) & (~BIT0)); +} + +void g2214_pc_connected(void) +{ + if (g_charger) + schedule_delayed_work(&g_charger->monitor_work, 1.5*HZ); +} + +static irqreturn_t otg_irq(int irq, void *data) +{ + struct g2214_charger *ch = data; + + if (REG8_VAL(USB_BASE_ADD + 0x7F1) & BIT7) { + REG8_VAL(USB_BASE_ADD + 0x7F1) = BIT7; + schedule_delayed_work(&ch->monitor_work, 0); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +// g2214 + dcdet use wakeup0 interrupt +static irqreturn_t g2214_dcdet_irq(int irq, void *data) +{ + struct g2214_charger *ch = data; + + // turn off the led immediately + if (!wmt_is_dc_plugin()) + led_power_enable(0); + + if (PMCIS_VAL & BIT0) { + pmc_clear_intr_status(WKS_WK0); + schedule_delayed_work(&ch->monitor_work, 0); + return IRQ_HANDLED; + } + + if (PMCIS_VAL & BIT27) { + pmc_clear_intr_status(WKS_DCDET); + schedule_delayed_work(&ch->monitor_work, 1.5*HZ); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static inline void pid_check_irq_enable(void) +{ + REG8_VAL(USB_BASE_ADD+0x7F2) |= BIT1; +} + +static inline void pid_check_irq_disable(void) +{ + REG8_VAL(USB_BASE_ADD+0x7F2) &= ~BIT1; +} + +static int irqs_init(struct g2214_charger *ch) +{ + unsigned long iflag = IRQF_SHARED; + int ret; + + ret = devm_request_irq(ch->dev, IRQ_UHDC, otg_irq, iflag, "USBOTG", ch); + if (ret < 0) { + dev_err(ch->dev, "IRQ_UHDC irq request failed %d\n", ret); + return ret; + } + + if (REG32_VAL(0xfe120000) == 0x35100101) + iflag |= IRQF_NO_SUSPEND; + + ret = devm_request_irq(ch->dev, IRQ_PMC_WAKEUP, g2214_dcdet_irq, iflag, + "G2214-DCDET", ch); + if (ret < 0) { + pr_err("register DCDET irq failed\n"); + return ret; + } + + pid_check_irq_enable(); + pmc_enable_wakeup_isr(WKS_WK0, 2); + wmt_dcdet_irq_enable(); + return 0; +} + +static void irqs_release(struct g2214_charger *ch) +{ + pid_check_irq_disable(); + wmt_dcdet_irq_disable(); + pmc_disable_wakeup_isr(WKS_WK0); +} + +static enum power_supply_property ac_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, +}; + +static int ac_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct g2214_charger *ch = + container_of(psy, struct g2214_charger, psy_ac); + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->ac_online; + break; + case POWER_SUPPLY_PROP_STATUS: + return g2214_read_status(ch, &val->intval); + default: + return -EINVAL; + } + return 0; +} + +static int usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct g2214_charger *ch = + container_of(psy, struct g2214_charger, psy_usb); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->usb_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *power_supplied_to[] = { + "battery", +}; + +static int power_supply_init(struct g2214_charger *ch) +{ + int ret; + + ch->psy_ac.name = "ac", + ch->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + ch->psy_ac.supplied_to = power_supplied_to, + ch->psy_ac.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_ac.properties = ac_properties, + ch->psy_ac.num_properties = ARRAY_SIZE(ac_properties), + ch->psy_ac.get_property = ac_get_property, + ret = power_supply_register(ch->dev, &ch->psy_ac); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING) { + ch->psy_usb.name = "usb", + ch->psy_usb.type = POWER_SUPPLY_TYPE_USB, + ch->psy_usb.supplied_to = power_supplied_to, + ch->psy_usb.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_usb.properties = usb_properties, + ch->psy_usb.num_properties = ARRAY_SIZE(usb_properties), + ch->psy_usb.get_property = usb_get_property, + ret = power_supply_register(ch->dev, &ch->psy_usb); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + return ret; + } + } + + return ret; +} + +static void power_supply_release(struct g2214_charger *ch) +{ + power_supply_unregister(&ch->psy_ac); +} + +#define G2214_PROC_NAME "driver/g2214_regs" + +static int g2214_proc_show(struct seq_file *seq, void *offset) +{ + int reg; + for (reg = REG_A0; reg <= REG_A13; reg++) + seq_printf(seq, "reg A%d: 0x%x\n ", + reg, g2214_read(g_charger, reg)); + return 0; +} + +static int g2214_proc_open(struct inode *inode, struct file *file) +{ + int ret; + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + + ret = single_open(file, g2214_proc_show, NULL); + if (ret) + module_put(THIS_MODULE); + return ret; +} + +static ssize_t g2214_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + char cmd[32]; + unsigned long len = count; + int reg, val; + + if (len > sizeof(cmd)) + len = sizeof(cmd); + + if (copy_from_user(cmd, buffer, len)) + return -EFAULT; + if (sscanf(cmd, "r%d=0x%02x", ®, &val) != 2) + return -EINVAL; + if (reg > REG_A13) + return -EINVAL; + + if (g_charger) { + g2214_write(g_charger, reg, val); + pr_info("## %s: reg %d -> 0x%02x\n", __func__, reg, val); + } + + return count; +} + +static int g2214_proc_release(struct inode *inode, struct file *file) +{ + int res = single_release(inode, file); + module_put(THIS_MODULE); + return res; +} + +static const struct file_operations g2214_proc_fops = { + .open = g2214_proc_open, + .read = seq_read, + .write = g2214_proc_write, + .llseek = seq_lseek, + .release = g2214_proc_release, +}; + +static inline void g2214_proc_setup(void) +{ + proc_create(G2214_PROC_NAME, 0, NULL, &g2214_proc_fops); +} + +static inline void g2214_proc_cleanup(void) +{ + remove_proc_entry(G2214_PROC_NAME, NULL); +} + +static int g2214_reboot_notifie(struct notifier_block *nb, unsigned long event, void *unused) +{ + struct g2214_charger *ch = g_charger; + cancel_delayed_work_sync(&ch->monitor_work); + g2214_enotg_config(ch, 0); + g2214_write(ch, REG_A9, 0xFF); + g2214_write(ch, REG_A11, 0xFF); + return NOTIFY_OK; +} + +static struct notifier_block g2214_reboot_notifier = { + .notifier_call = g2214_reboot_notifie, +}; + + +static int __devinit g2214_probe(struct platform_device *pdev) +{ + struct g2214_charger *ch; + int ret; + + ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + if ((ret = parse_charger_param(ch))) + return ret; + parse_charger_led(); + + ch->dev = &pdev->dev; + ch->gmt_dev = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, ch); + + if ((ret = power_supply_init(ch))) + return ret; + + INIT_DELAYED_WORK(&ch->monitor_work, charger_monitor_work); + + if ((ret = irqs_init(ch))) { + power_supply_release(ch); + return ret; + } + + g_charger = ch; + g2214_reg_init(ch); + g2214_proc_setup(); + + register_reboot_notifier(&g2214_reboot_notifier); + schedule_delayed_work(&ch->monitor_work, 0); + pr_info(DRVNAME " install success.\n"); + return 0; +} + +static int __devexit g2214_remove(struct platform_device *pdev) +{ + struct g2214_charger *ch = platform_get_drvdata(pdev); + irqs_release(ch); + cancel_delayed_work_sync(&ch->monitor_work); + power_supply_release(ch); + g2214_proc_cleanup(); + g_charger = NULL; + return 0; +} + +static int g2214_suspend(struct device *dev) +{ + struct g2214_charger *ch = dev_get_drvdata(dev); + cancel_delayed_work_sync(&ch->monitor_work); + ch->sleeping = 1; + current_refresh(ch); + g2214_write(ch, REG_A11, 0xCF); + return 0; +} + +static int g2214_resume(struct device *dev) +{ + struct g2214_charger *ch = dev_get_drvdata(dev); + + // turn off the led immediately + if (!wmt_is_dc_plugin()) + led_power_enable(0); + + pid_check_irq_enable(); + ch->sleeping = 0; + current_refresh(ch); + schedule_delayed_work(&ch->monitor_work, HZ); + g2214_write(ch, REG_A11, 0xFF); + return 0; +} + +static const struct dev_pm_ops g2214_pm_ops = { + .suspend = g2214_suspend, + .resume = g2214_resume, +}; + +static struct platform_driver g2214_driver = { + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + .pm = &g2214_pm_ops, + }, + .probe = g2214_probe, + .remove = __devexit_p(g2214_remove), +}; + +static int __init g2214_init(void) +{ + if (parse_charger_param(NULL)) + return -ENODEV; + return platform_driver_register(&g2214_driver); +} + +static void __exit g2214_exit(void) +{ + return platform_driver_unregister(&g2214_driver); +} + +module_init(g2214_init); +module_exit(g2214_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("GMT2144 battery charger driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("i2c:g2214"); + diff --git a/drivers/power/wmt_battery/charger/mp2625/Kconfig b/drivers/power/wmt_battery/charger/mp2625/Kconfig new file mode 100755 index 00000000..38282c65 --- /dev/null +++ b/drivers/power/wmt_battery/charger/mp2625/Kconfig @@ -0,0 +1,5 @@ +config MP2625_CHARGER
+ tristate "MP2625 Charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for charger with mp2625 chip.
diff --git a/drivers/power/wmt_battery/charger/mp2625/Makefile b/drivers/power/wmt_battery/charger/mp2625/Makefile new file mode 100755 index 00000000..9bcfa83d --- /dev/null +++ b/drivers/power/wmt_battery/charger/mp2625/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for MP2625 charger core. +# + +obj-y += mp2625_charger.o + diff --git a/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c b/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c new file mode 100755 index 00000000..581e89d8 --- /dev/null +++ b/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c @@ -0,0 +1,428 @@ +/* + * mp2625_charger.c - WonderMedia Charger Driver. + * + * Copyright (C) 2013 WonderMedia Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <mach/wmt_env.h> +#include <mach/hardware.h> +#include <mach/wmt_iomux.h> +#include <linux/gpio.h> +#include <linux/power/wmt_battery.h> + +#define DRVNAME "mp2625-charger" + +#undef pr_err +#undef pr_info +#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) +#define pr_info(fmt, args...) printk("[" DRVNAME "] " fmt, ##args) + +struct mp2625_charger { + struct device *dev; + int ac_online; + int usb_online; + int charger_status; + int sleeping; + + union { + struct { + unsigned int cable_type:4; + unsigned int current_sw_mode:1; + unsigned int pc_charging:1; + }; + uint32_t flag; + }; + + int full_pin; + int full_level; + int current_pin; + int current_large_level; + + struct power_supply psy_ac; + struct power_supply psy_usb; + struct delayed_work dwork; +}; + +static struct mp2625_charger *g_charger; + +static int parse_charger_param(struct mp2625_charger *ch) +{ + static char uboot_env[] = "wmt.charger.param"; + char buf[64]; + size_t l = sizeof(buf); + int ret; + + if (wmt_getsyspara(uboot_env, buf, &l)) + return -ENODEV; + if (prefixcmp(buf, "mp2625:")) + return -ENODEV; + if (!ch) + return 0; + + ret = sscanf(buf + 7, "%x:%d:%d:%d:%d", + &ch->flag, + &ch->full_pin, &ch->full_level, + &ch->current_pin, &ch->current_large_level); + if (ret < 5) { + pr_err("Invalid uboot env: %s\n", uboot_env); + return -EINVAL; + } + + if (ch->cable_type != CABLE_TYPE_DC && + ch->cable_type != CABLE_TYPE_USB) { + pr_err("Invalid type %d\n", ch->cable_type); + return -EINVAL; + } + + if (gpio_is_valid(ch->full_pin)) { + ret = devm_gpio_request(ch->dev, ch->full_pin, "charger full"); + if (ret) { + pr_err("gpio%d request fail %d\n", ch->full_pin, ret); + return ret; + } + wmt_gpio_setpull(ch->full_pin, (ch->full_level) ? + WMT_GPIO_PULL_DOWN : WMT_GPIO_PULL_UP); + gpio_direction_input(ch->full_pin); + } + + if (gpio_is_valid(ch->current_pin)) { + ret = devm_gpio_request(ch->dev, ch->current_pin, "charger current"); + if (ret) { + pr_err("gpio%d request fail %d\n", ch->current_pin, ret); + return ret; + } + gpio_direction_output(ch->current_pin, !ch->current_large_level); + } + + pr_info("charger match " DRVNAME ", %s cable, full %d, current %d\n" + "%s current switch, PC connected is %scharging\n", + (ch->cable_type == CABLE_TYPE_DC) ? "DC" : "USB", + ch->full_pin, ch->current_pin, + (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) ? "dynamic" : "sleep", + (ch->pc_charging == PC_CONNECTED_NOT_CHARGING) ? "not " : ""); + return 0; +} + +static inline void set_current(struct mp2625_charger *ch) +{ + int large; + int charging = (ch->charger_status == POWER_SUPPLY_STATUS_CHARGING || + ch->charger_status == POWER_SUPPLY_STATUS_FULL); + + if (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) + large = charging; + else + large = ch->sleeping ? 1 : 0; + + if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) + large = 0; + + if (gpio_is_valid(ch->current_pin)) { + gpio_direction_output(ch->current_pin, + large ? ch->current_large_level : + !ch->current_large_level); + printk(KERN_DEBUG "set %s current\n", large ? "large" : "small"); + } +} + +static int ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mp2625_charger *ch = + container_of(psy, struct mp2625_charger, psy_ac); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->ac_online; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = ch->charger_status; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property ac_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, +}; + +static int usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mp2625_charger *ch = + container_of(psy, struct mp2625_charger, psy_usb); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ch->usb_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property usb_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *power_supplied_to[] = { + "battery", +}; + +static int power_supply_init(struct mp2625_charger *ch) +{ + int ret; + + ch->psy_ac.name = "ac", + ch->psy_ac.type = POWER_SUPPLY_TYPE_MAINS, + ch->psy_ac.supplied_to = power_supplied_to, + ch->psy_ac.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_ac.properties = ac_properties, + ch->psy_ac.num_properties = ARRAY_SIZE(ac_properties), + ch->psy_ac.get_property = ac_get_property, + ret = power_supply_register(ch->dev, &ch->psy_ac); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + return ret; + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING) { + ch->psy_usb.name = "usb", + ch->psy_usb.type = POWER_SUPPLY_TYPE_USB, + ch->psy_usb.supplied_to = power_supplied_to, + ch->psy_usb.num_supplicants = ARRAY_SIZE(power_supplied_to), + ch->psy_usb.properties = usb_properties, + ch->psy_usb.num_properties = ARRAY_SIZE(usb_properties), + ch->psy_usb.get_property = usb_get_property, + ret = power_supply_register(ch->dev, &ch->psy_usb); + if (ret) { + dev_err(ch->dev, "register ac power supply failed.\n"); + return ret; + } + } + + return 0; +} + +static void power_supply_release(struct mp2625_charger *ch) +{ + power_supply_unregister(&ch->psy_ac); + if (ch->pc_charging == PC_CONNECTED_CHARGING) + power_supply_unregister(&ch->psy_usb); +} + +static void mp2625_charger_work(struct work_struct *work) +{ + struct mp2625_charger *ch = + container_of(work, struct mp2625_charger, dwork.work); + int ac_online = 0; + int usb_online = 0; + int charger_status = 0; + + if (wmt_is_dc_plugin()) { + if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) { + if (ch->pc_charging == PC_CONNECTED_CHARGING) { + charger_status = POWER_SUPPLY_STATUS_CHARGING; + usb_online = 1; + } else + charger_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + charger_status = POWER_SUPPLY_STATUS_CHARGING; + ac_online = 1; + } + + if (charger_status == POWER_SUPPLY_STATUS_CHARGING && + gpio_get_value(ch->full_pin) == ch->full_level) + charger_status = POWER_SUPPLY_STATUS_FULL; + } else + charger_status = POWER_SUPPLY_STATUS_DISCHARGING; + + if (ch->ac_online != ac_online || + ch->charger_status != charger_status) { + ch->ac_online = ac_online; + ch->charger_status = charger_status; + power_supply_changed(&ch->psy_ac); + } + + if (ch->pc_charging == PC_CONNECTED_CHARGING && + ch->usb_online != usb_online) { + ch->usb_online = usb_online; + power_supply_changed(&ch->psy_usb); + } + + set_current(ch); + led_power_enable(charger_status == POWER_SUPPLY_STATUS_CHARGING || + charger_status == POWER_SUPPLY_STATUS_FULL); +} + +void mp2625_pc_connected(void) +{ + if (g_charger) + schedule_delayed_work(&g_charger->dwork, 0); +} + +static irqreturn_t dcdet_irq(int irq, void *data) +{ + struct mp2625_charger *ch = data; + + if (PMCIS_VAL & BIT27) { + pmc_clear_intr_status(WKS_DCDET); + schedule_delayed_work(&ch->dwork, HZ/2); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int irqs_init(struct mp2625_charger *ch) +{ + unsigned long iflag = IRQF_SHARED; + int ret; + + if (REG32_VAL(0xfe120000) == 0x35100101) + iflag |= IRQF_NO_SUSPEND; + + ret = devm_request_irq(ch->dev, IRQ_PMC_WAKEUP, dcdet_irq, + iflag, "WMT-DCDET", ch); + if (ret < 0) { + pr_err("register DCDET irq failed\n"); + return ret; + } + + wmt_dcdet_irq_enable(); + return 0; +} + +static void irqs_release(struct mp2625_charger *ch) +{ + wmt_dcdet_irq_disable(); +} + +static int mp2625_probe(struct platform_device *pdev) +{ + struct mp2625_charger *ch; + int ret; + + ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + ch->dev = &pdev->dev; + platform_set_drvdata(pdev, ch); + + ret = parse_charger_param(ch); + if (ret) + return ret;; + parse_charger_led(); + + if ((ret = power_supply_init(ch))) + return ret; + + INIT_DELAYED_WORK(&ch->dwork, mp2625_charger_work); + + if ((ret = irqs_init(ch))) { + power_supply_release(ch); + return ret; + } + + g_charger = ch; + schedule_delayed_work(&ch->dwork, 0); + + pr_info(DRVNAME " install success.\n"); + return 0; +} + +static int __devexit mp2625_remove(struct platform_device *pdev) +{ + struct mp2625_charger *ch = platform_get_drvdata(pdev); + irqs_release(ch); + cancel_delayed_work_sync(&ch->dwork); + power_supply_release(ch); + g_charger = NULL; + return 0; +} + +static int mp2625_suspend(struct device *dev) +{ + struct mp2625_charger *ch = dev_get_drvdata(dev); + cancel_delayed_work_sync(&ch->dwork); + ch->sleeping = 1; + set_current(ch); + return 0; +} + +static int mp2625_resume(struct device *dev) +{ + struct mp2625_charger *ch = dev_get_drvdata(dev); + schedule_delayed_work(&ch->dwork, HZ/2); + ch->sleeping = 0; + set_current(ch); + return 0; +} + +static const struct dev_pm_ops mp2625_pm_ops = { + .suspend = mp2625_suspend, + .resume = mp2625_resume, +}; + +static struct platform_driver mp2625_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + .pm = &mp2625_pm_ops, + }, + .probe = mp2625_probe, + .remove = mp2625_remove, +}; + +static struct platform_device *pdev; + +static int __init mp2625_init(void) +{ + int ret; + + ret = parse_charger_param(NULL); + if (ret) + return ret; + + ret = platform_driver_register(&mp2625_driver); + if (ret) + return ret; + + pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + platform_driver_unregister(&mp2625_driver); + } + return ret; +} + +static void __exit mp2625_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&mp2625_driver); +} + +module_init(mp2625_init); +module_exit(mp2625_exit); + +MODULE_AUTHOR("WonderMedia"); +MODULE_DESCRIPTION("MP2625 Charger Driver"); +MODULE_LICENSE("GPL"); + |