341 lines
8.9 KiB
C
341 lines
8.9 KiB
C
/*
|
|
* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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) "%s: " fmt, __func__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/power/ltc4088-charger.h>
|
|
|
|
#define MAX_CURRENT_UA(n) (n)
|
|
#define MAX_CURRENT_MA(n) (n * MAX_CURRENT_UA(1000))
|
|
|
|
/**
|
|
* ltc4088_max_current - A typical current values supported by the charger
|
|
* @LTC4088_MAX_CURRENT_100mA: 100mA current
|
|
* @LTC4088_MAX_CURRENT_500mA: 500mA current
|
|
* @LTC4088_MAX_CURRENT_1A: 1A current
|
|
*/
|
|
enum ltc4088_max_current {
|
|
LTC4088_MAX_CURRENT_100mA = 100,
|
|
LTC4088_MAX_CURRENT_500mA = 500,
|
|
LTC4088_MAX_CURRENT_1A = 1000,
|
|
};
|
|
|
|
/**
|
|
* struct ltc4088_chg_chip - Device information
|
|
* @dev: Device pointer to access the parent
|
|
* @lock: Enable mutual exclusion
|
|
* @usb_psy: USB device information
|
|
* @gpio_mode_select_d0: GPIO #pin for D0 charger line
|
|
* @gpio_mode_select_d1: GPIO #pin for D1 charger line
|
|
* @gpio_mode_select_d2: GPIO #pin for D2 charger line
|
|
* @max_current: Maximum current that is supplied at this time
|
|
*/
|
|
struct ltc4088_chg_chip {
|
|
struct device *dev;
|
|
struct mutex lock;
|
|
struct power_supply usb_psy;
|
|
unsigned int gpio_mode_select_d0;
|
|
unsigned int gpio_mode_select_d1;
|
|
unsigned int gpio_mode_select_d2;
|
|
unsigned int max_current;
|
|
};
|
|
|
|
static enum power_supply_property pm_power_props[] = {
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
};
|
|
|
|
static char *pm_power_supplied_to[] = {
|
|
"battery",
|
|
};
|
|
|
|
static int ltc4088_set_charging(struct ltc4088_chg_chip *chip, bool enable)
|
|
{
|
|
mutex_lock(&chip->lock);
|
|
|
|
if (enable) {
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d2, 0);
|
|
} else {
|
|
/* When disabling charger, set the max current to 0 also */
|
|
chip->max_current = 0;
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d2, 1);
|
|
}
|
|
|
|
mutex_unlock(&chip->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ltc4088_set_max_current(struct ltc4088_chg_chip *chip, int value)
|
|
{
|
|
mutex_lock(&chip->lock);
|
|
|
|
/* If current is less than 100mA, we can not support that granularity */
|
|
if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA)) {
|
|
chip->max_current = 0;
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
|
|
} else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA)) {
|
|
chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0);
|
|
} else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A)) {
|
|
chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
|
|
} else {
|
|
chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0);
|
|
}
|
|
|
|
mutex_unlock(&chip->lock);
|
|
}
|
|
|
|
static void ltc4088_set_charging_off(struct ltc4088_chg_chip *chip)
|
|
{
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
|
|
gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
|
|
}
|
|
|
|
static int ltc4088_set_initial_state(struct ltc4088_chg_chip *chip)
|
|
{
|
|
int rc;
|
|
|
|
rc = gpio_request(chip->gpio_mode_select_d0, "ltc4088_D0");
|
|
if (rc) {
|
|
pr_err("gpio request failed for GPIO %d\n",
|
|
chip->gpio_mode_select_d0);
|
|
return rc;
|
|
}
|
|
|
|
rc = gpio_request(chip->gpio_mode_select_d1, "ltc4088_D1");
|
|
if (rc) {
|
|
pr_err("gpio request failed for GPIO %d\n",
|
|
chip->gpio_mode_select_d1);
|
|
goto gpio_err_d0;
|
|
}
|
|
|
|
rc = gpio_request(chip->gpio_mode_select_d2, "ltc4088_D2");
|
|
if (rc) {
|
|
pr_err("gpio request failed for GPIO %d\n",
|
|
chip->gpio_mode_select_d2);
|
|
goto gpio_err_d1;
|
|
}
|
|
|
|
rc = gpio_direction_output(chip->gpio_mode_select_d0, 0);
|
|
if (rc) {
|
|
pr_err("failed to set direction for GPIO %d\n",
|
|
chip->gpio_mode_select_d0);
|
|
goto gpio_err_d2;
|
|
}
|
|
|
|
rc = gpio_direction_output(chip->gpio_mode_select_d1, 0);
|
|
if (rc) {
|
|
pr_err("failed to set direction for GPIO %d\n",
|
|
chip->gpio_mode_select_d1);
|
|
goto gpio_err_d2;
|
|
}
|
|
|
|
rc = gpio_direction_output(chip->gpio_mode_select_d2, 1);
|
|
if (rc) {
|
|
pr_err("failed to set direction for GPIO %d\n",
|
|
chip->gpio_mode_select_d2);
|
|
goto gpio_err_d2;
|
|
}
|
|
|
|
return 0;
|
|
|
|
gpio_err_d2:
|
|
gpio_free(chip->gpio_mode_select_d2);
|
|
gpio_err_d1:
|
|
gpio_free(chip->gpio_mode_select_d1);
|
|
gpio_err_d0:
|
|
gpio_free(chip->gpio_mode_select_d0);
|
|
return rc;
|
|
}
|
|
|
|
static int pm_power_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct ltc4088_chg_chip *chip;
|
|
|
|
if (psy->type == POWER_SUPPLY_TYPE_USB) {
|
|
chip = container_of(psy, struct ltc4088_chg_chip,
|
|
usb_psy);
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
if (chip->max_current)
|
|
val->intval = 1;
|
|
else
|
|
val->intval = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
val->intval = chip->max_current;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int pm_power_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct ltc4088_chg_chip *chip;
|
|
|
|
if (psy->type == POWER_SUPPLY_TYPE_USB) {
|
|
chip = container_of(psy, struct ltc4088_chg_chip, usb_psy);
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_TYPE:
|
|
psy.type = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
ltc4088_set_charging(chip, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
ltc4088_set_max_current(chip, val->intval);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit ltc4088_charger_probe(struct platform_device *pdev)
|
|
{
|
|
int rc;
|
|
struct ltc4088_chg_chip *chip;
|
|
const struct ltc4088_charger_platform_data *pdata
|
|
= pdev->dev.platform_data;
|
|
|
|
if (!pdata) {
|
|
pr_err("missing platform data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip = kzalloc(sizeof(struct ltc4088_chg_chip),
|
|
GFP_KERNEL);
|
|
if (!chip) {
|
|
pr_err("Cannot allocate pm_chg_chip\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->dev = &pdev->dev;
|
|
|
|
if (pdata->gpio_mode_select_d0 < 0 ||
|
|
pdata->gpio_mode_select_d1 < 0 ||
|
|
pdata->gpio_mode_select_d2 < 0) {
|
|
pr_err("Invalid platform data supplied\n");
|
|
rc = -EINVAL;
|
|
goto free_chip;
|
|
}
|
|
|
|
mutex_init(&chip->lock);
|
|
|
|
chip->gpio_mode_select_d0 = pdata->gpio_mode_select_d0;
|
|
chip->gpio_mode_select_d1 = pdata->gpio_mode_select_d1;
|
|
chip->gpio_mode_select_d2 = pdata->gpio_mode_select_d2;
|
|
|
|
chip->usb_psy.name = "usb",
|
|
chip->usb_psy.type = POWER_SUPPLY_TYPE_USB,
|
|
chip->usb_psy.supplied_to = pm_power_supplied_to,
|
|
chip->usb_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to),
|
|
chip->usb_psy.properties = pm_power_props,
|
|
chip->usb_psy.num_properties = ARRAY_SIZE(pm_power_props),
|
|
chip->usb_psy.get_property = pm_power_get_property,
|
|
chip->usb_psy.set_property = pm_power_set_property,
|
|
|
|
rc = power_supply_register(chip->dev, &chip->usb_psy);
|
|
if (rc < 0) {
|
|
pr_err("power_supply_register usb failed rc = %d\n", rc);
|
|
goto free_chip;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, chip);
|
|
|
|
rc = ltc4088_set_initial_state(chip);
|
|
if (rc < 0) {
|
|
pr_err("setting initial state failed rc = %d\n", rc);
|
|
goto unregister_usb;
|
|
}
|
|
|
|
return 0;
|
|
|
|
unregister_usb:
|
|
platform_set_drvdata(pdev, NULL);
|
|
power_supply_unregister(&chip->usb_psy);
|
|
free_chip:
|
|
kfree(chip);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __devexit ltc4088_charger_remove(struct platform_device *pdev)
|
|
{
|
|
struct ltc4088_chg_chip *chip = platform_get_drvdata(pdev);
|
|
|
|
ltc4088_set_charging_off(chip);
|
|
|
|
gpio_free(chip->gpio_mode_select_d2);
|
|
gpio_free(chip->gpio_mode_select_d1);
|
|
gpio_free(chip->gpio_mode_select_d0);
|
|
|
|
power_supply_unregister(&chip->usb_psy);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
mutex_destroy(&chip->lock);
|
|
kfree(chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver ltc4088_charger_driver = {
|
|
.probe = ltc4088_charger_probe,
|
|
.remove = __devexit_p(ltc4088_charger_remove),
|
|
.driver = {
|
|
.name = LTC4088_CHARGER_DEV_NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init ltc4088_charger_init(void)
|
|
{
|
|
return platform_driver_register(<c4088_charger_driver);
|
|
}
|
|
|
|
static void __exit ltc4088_charger_exit(void)
|
|
{
|
|
platform_driver_unregister(<c4088_charger_driver);
|
|
}
|
|
|
|
subsys_initcall(ltc4088_charger_init);
|
|
module_exit(ltc4088_charger_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("LTC4088 charger/battery driver");
|
|
MODULE_VERSION("1.0");
|
|
MODULE_ALIAS("platform:" LTC4088_CHARGER_DEV_NAME);
|