| /* |
| * bq24260_charger.c |
| * Samsung bq24260 Charger Driver |
| * |
| * Copyright (C) 2012 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 DEBUG |
| |
| #include <linux/battery/sec_charger.h> |
| |
| static int bq24260_i2c_write(struct i2c_client *client, |
| int reg, u8 *buf) |
| { |
| int ret; |
| ret = i2c_smbus_write_i2c_block_data(client, reg, 1, buf); |
| if (ret < 0) |
| dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); |
| return ret; |
| } |
| |
| static int bq24260_i2c_read(struct i2c_client *client, |
| int reg, u8 *buf) |
| { |
| int ret; |
| ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); |
| if (ret < 0) |
| dev_err(&client->dev, "%s: Error(%d)\n", __func__, ret); |
| return ret; |
| } |
| |
| static void bq24260_i2c_write_array(struct i2c_client *client, |
| u8 *buf, int size) |
| { |
| int i; |
| for (i = 0; i < size; i += 3) |
| bq24260_i2c_write(client, (u8) (*(buf + i)), (buf + i) + 1); |
| } |
| |
| static void bq24260_set_command(struct i2c_client *client, |
| int reg, int datum) |
| { |
| int val; |
| u8 data = 0; |
| val = bq24260_i2c_read(client, reg, &data); |
| if (val >= 0) { |
| dev_dbg(&client->dev, "%s : reg(0x%02x): 0x%02x(0x%02x)", |
| __func__, reg, data, datum); |
| if (data != datum) { |
| data = datum; |
| if (bq24260_i2c_write(client, reg, &data) < 0) |
| dev_err(&client->dev, |
| "%s : error!\n", __func__); |
| val = bq24260_i2c_read(client, reg, &data); |
| if (val >= 0) |
| dev_dbg(&client->dev, " => 0x%02x\n", data); |
| } |
| } |
| } |
| |
| static void bq24260_test_read(struct i2c_client *client) |
| { |
| u8 data = 0; |
| u32 addr = 0; |
| for (addr = 0; addr <= 0x06; addr++) { |
| bq24260_i2c_read(client, addr, &data); |
| dev_dbg(&client->dev, |
| "bq24260 addr : 0x%02x data : 0x%02x\n", addr, data); |
| } |
| } |
| |
| static void bq24260_read_regs(struct i2c_client *client, char *str) |
| { |
| u8 data = 0; |
| u32 addr = 0; |
| |
| for (addr = 0; addr <= 0x06; addr++) { |
| bq24260_i2c_read(client, addr, &data); |
| sprintf(str+strlen(str), "0x%x, ", data); |
| } |
| } |
| |
| |
| static int bq24260_get_charging_status(struct i2c_client *client) |
| { |
| int status = POWER_SUPPLY_STATUS_UNKNOWN; |
| u8 data = 0; |
| |
| bq24260_i2c_read(client, BQ24260_STATUS, &data); |
| dev_info(&client->dev, |
| "%s : charger status(0x%02x)\n", __func__, data); |
| |
| data = (data & 0x30); |
| |
| switch (data) { |
| case 0x00: |
| status = POWER_SUPPLY_STATUS_DISCHARGING; |
| break; |
| case 0x10: |
| status = POWER_SUPPLY_STATUS_CHARGING; |
| break; |
| case 0x20: |
| status = POWER_SUPPLY_STATUS_FULL; |
| break; |
| case 0x30: |
| status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| break; |
| } |
| |
| return (int)status; |
| } |
| |
| static int bq24260_get_charging_health(struct i2c_client *client) |
| { |
| int health = POWER_SUPPLY_HEALTH_GOOD; |
| u8 data = 0; |
| |
| bq24260_i2c_read(client, BQ24260_STATUS, &data); |
| dev_info(&client->dev, |
| "%s : charger status(0x%02x)\n", __func__, data); |
| |
| if ((data & 0x30) == 0x30) { /* check for fault */ |
| data = (data & 0x07); |
| |
| switch (data) { |
| case 0x01: |
| health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| break; |
| case 0x02: |
| health = POWER_SUPPLY_HEALTH_UNDERVOLTAGE; |
| break; |
| } |
| } |
| |
| return (int)health; |
| } |
| |
| static u8 bq24260_get_float_voltage_data( |
| int float_voltage) |
| { |
| u8 data; |
| |
| if (float_voltage < 3500) |
| float_voltage = 3500; |
| |
| data = (float_voltage - 3500) / 20; |
| |
| return data << 2; |
| } |
| |
| static u8 bq24260_get_input_current_limit_data( |
| int input_current) |
| { |
| u8 data = 0x00; |
| |
| if (input_current <= 100) |
| data = 0x00; |
| else if (input_current <= 150) |
| data = 0x01; |
| else if (input_current <= 500) |
| data = 0x02; |
| else if (input_current <= 900) |
| data = 0x03; |
| else if (input_current <= 1000) |
| data = 0x04; |
| else if (input_current <= 2000)/* will be set as 1950mA */ |
| data = 0x06; |
| else /* No limit */ |
| data = 0x07; |
| |
| return data << 4; |
| } |
| |
| static u8 bq24260_get_termination_current_limit_data( |
| int termination_current) |
| { |
| u8 data; |
| |
| /* default offset 50mA, max 300mA */ |
| data = (termination_current - 50) / 50; |
| |
| return data; |
| } |
| |
| static u8 bq24260_get_fast_charging_current_data( |
| int fast_charging_current) |
| { |
| u8 data; |
| |
| /* default offset 500mA */ |
| if (fast_charging_current < 500) |
| fast_charging_current = 500; |
| |
| data = (fast_charging_current - 500) / 100; |
| |
| return data << 3; |
| } |
| |
| static void bq24260_charger_function_conrol( |
| struct i2c_client *client) |
| { |
| struct sec_charger_info *charger = i2c_get_clientdata(client); |
| union power_supply_propval val; |
| int full_check_type; |
| u8 data; |
| if (charger->charging_current < 0) { |
| dev_dbg(&client->dev, |
| "%s : OTG is activated. Ignore command!\n", __func__); |
| return; |
| } |
| |
| if (charger->cable_type == |
| POWER_SUPPLY_TYPE_BATTERY) { |
| data = 0x00; |
| bq24260_i2c_read(client, BQ24260_CONTROL, &data); |
| data |= 0x2; |
| data &= 0x7f; /* Prevent register reset */ |
| bq24260_set_command(client, |
| BQ24260_CONTROL, data); |
| } else { |
| data = 0x00; |
| bq24260_i2c_read(client, BQ24260_CONTROL, &data); |
| /* Enable charging */ |
| data &= 0x7d; /*default enabled*/ |
| psy_do_property("battery", get, |
| POWER_SUPPLY_PROP_CHARGE_NOW, val); |
| if (val.intval == SEC_BATTERY_CHARGING_1ST) |
| full_check_type = charger->pdata->full_check_type; |
| else |
| full_check_type = charger->pdata->full_check_type_2nd; |
| /* Termination setting */ |
| switch (full_check_type) { |
| case SEC_BATTERY_FULLCHARGED_CHGGPIO: |
| case SEC_BATTERY_FULLCHARGED_CHGINT: |
| case SEC_BATTERY_FULLCHARGED_CHGPSY: |
| /* Enable Current Termination */ |
| data |= 0x04; |
| break; |
| default: |
| data &= 0x7b; |
| break; |
| } |
| /* Input current limit */ |
| dev_dbg(&client->dev, "%s : input current (%dmA)\n", |
| __func__, charger->pdata->charging_current |
| [charger->cable_type].input_current_limit); |
| data &= 0x0F; |
| data |= bq24260_get_input_current_limit_data( |
| charger->pdata->charging_current |
| [charger->cable_type].input_current_limit); |
| bq24260_set_command(client, |
| BQ24260_CONTROL, data); |
| |
| data = 0x00; |
| /* Float voltage */ |
| dev_dbg(&client->dev, "%s : float voltage (%dmV)\n", |
| __func__, charger->pdata->chg_float_voltage); |
| data |= bq24260_get_float_voltage_data( |
| charger->pdata->chg_float_voltage); |
| bq24260_set_command(client, |
| BQ24260_VOLTAGE, data); |
| |
| data = 0x00; |
| /* Fast charge and Termination current */ |
| dev_dbg(&client->dev, "%s : fast charging current (%dmA)\n", |
| __func__, charger->charging_current); |
| data |= bq24260_get_fast_charging_current_data( |
| charger->charging_current); |
| dev_dbg(&client->dev, "%s : termination current (%dmA)\n", |
| __func__, charger->pdata->charging_current[ |
| charger->cable_type].full_check_current_1st >= 300 ? |
| 300 : charger->pdata->charging_current[ |
| charger->cable_type].full_check_current_1st); |
| data |= bq24260_get_termination_current_limit_data( |
| charger->pdata->charging_current[ |
| charger->cable_type].full_check_current_1st); |
| bq24260_set_command(client, |
| BQ24260_CURRENT, data); |
| |
| /* Special Charger Voltage |
| * Normal charge current |
| */ |
| bq24260_i2c_read(client, BQ24260_SPECIAL, &data); |
| data &= 0xdf; |
| bq24260_set_command(client, |
| BQ24260_SPECIAL, data); |
| } |
| } |
| |
| static void bq24260_charger_otg_conrol( |
| struct i2c_client *client) |
| { |
| struct sec_charger_info *charger = i2c_get_clientdata(client); |
| u8 data; |
| if (charger->cable_type == |
| POWER_SUPPLY_TYPE_BATTERY) { |
| dev_info(&client->dev, "%s : turn off OTG\n", __func__); |
| /* turn off OTG */ |
| bq24260_i2c_read(client, BQ24260_STATUS, &data); |
| data &= 0xbf; |
| bq24260_set_command(client, |
| BQ24260_STATUS, data); |
| } else { |
| dev_info(&client->dev, "%s : turn on OTG\n", __func__); |
| /* turn on OTG */ |
| bq24260_i2c_read(client, BQ24260_STATUS, &data); |
| data |= 0x40; |
| bq24260_set_command(client, |
| BQ24260_STATUS, data); |
| } |
| } |
| |
| static int bq24260_get_charge_type(struct i2c_client *client) |
| { |
| int ret; |
| u8 data; |
| |
| bq24260_i2c_read(client, BQ24260_STATUS, &data); |
| data = (data & 0x30)>>4; |
| |
| switch (data) { |
| case 0x01: |
| ret = POWER_SUPPLY_CHARGE_TYPE_FAST; |
| break; |
| default: |
| ret = POWER_SUPPLY_CHARGE_TYPE_NONE; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| bool sec_hal_chg_init(struct i2c_client *client) |
| { |
| bq24260_test_read(client); |
| return true; |
| } |
| |
| bool sec_hal_chg_suspend(struct i2c_client *client) |
| { |
| return true; |
| } |
| |
| bool sec_hal_chg_resume(struct i2c_client *client) |
| { |
| return true; |
| } |
| |
| bool sec_hal_chg_get_property(struct i2c_client *client, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct sec_charger_info *charger = i2c_get_clientdata(client); |
| u8 data; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = bq24260_get_charging_status(client); |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_TYPE: |
| val->intval = bq24260_get_charge_type(client); |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| val->intval = bq24260_get_charging_health(client); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| if (charger->charging_current) { |
| /* Rsns 0.068 Ohm */ |
| bq24260_i2c_read(client, BQ24260_CURRENT, &data); |
| val->intval = (data >> 3) * 100 + 500; |
| } else |
| val->intval = 0; |
| dev_dbg(&client->dev, |
| "%s : set-current(%dmA), current now(%dmA)\n", |
| __func__, charger->charging_current, val->intval); |
| break; |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| bool sec_hal_chg_set_property(struct i2c_client *client, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct sec_charger_info *charger = i2c_get_clientdata(client); |
| |
| switch (psp) { |
| /* val->intval : type */ |
| case POWER_SUPPLY_PROP_ONLINE: |
| if (charger->pdata->chg_gpio_en) { |
| if (gpio_request(charger->pdata->chg_gpio_en, |
| "CHG_EN") < 0) { |
| dev_err(&client->dev, |
| "failed to request vbus_in gpio\n"); |
| break; |
| } |
| if (charger->cable_type == |
| POWER_SUPPLY_TYPE_BATTERY) |
| gpio_set_value_cansleep( |
| charger->pdata->chg_gpio_en, |
| charger->pdata->chg_polarity_en ? |
| 0 : 1); |
| else |
| gpio_set_value_cansleep( |
| charger->pdata->chg_gpio_en, |
| charger->pdata->chg_polarity_en ? |
| 1 : 0); |
| gpio_free(charger->pdata->chg_gpio_en); |
| } |
| /* val->intval : charging current */ |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| if (charger->charging_current < 0) |
| bq24260_charger_otg_conrol(client); |
| else if (charger->charging_current > 0) |
| bq24260_charger_function_conrol(client); |
| else { |
| bq24260_charger_function_conrol(client); |
| bq24260_charger_otg_conrol(client); |
| } |
| bq24260_test_read(client); |
| break; |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| ssize_t sec_hal_chg_show_attrs(struct device *dev, |
| const ptrdiff_t offset, char *buf) |
| { |
| struct power_supply *psy = dev_get_drvdata(dev); |
| struct sec_charger_info *chg = |
| container_of(psy, struct sec_charger_info, psy_chg); |
| int i = 0; |
| char *str = NULL; |
| |
| switch (offset) { |
| case CHG_REG: |
| i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", |
| chg->reg_addr); |
| break; |
| case CHG_DATA: |
| i += scnprintf(buf + i, PAGE_SIZE - i, "%x\n", |
| chg->reg_data); |
| break; |
| case CHG_REGS: |
| str = kzalloc(sizeof(char)*1024, GFP_KERNEL); |
| if (!str) |
| return -ENOMEM; |
| |
| bq24260_read_regs(chg->client, str); |
| i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", |
| str); |
| |
| kfree(str); |
| break; |
| default: |
| i = -EINVAL; |
| break; |
| } |
| |
| return i; |
| } |
| |
| ssize_t sec_hal_chg_store_attrs(struct device *dev, |
| const ptrdiff_t offset, |
| const char *buf, size_t count) |
| { |
| struct power_supply *psy = dev_get_drvdata(dev); |
| struct sec_charger_info *chg = |
| container_of(psy, struct sec_charger_info, psy_chg); |
| int ret = 0; |
| int x = 0; |
| u8 data = 0; |
| |
| switch (offset) { |
| case CHG_REG: |
| if (sscanf(buf, "%x\n", &x) == 1) { |
| chg->reg_addr = x; |
| bq24260_i2c_read(chg->client, |
| chg->reg_addr, &data); |
| chg->reg_data = data; |
| dev_dbg(dev, "%s: (read) addr = 0x%x, data = 0x%x\n", |
| __func__, chg->reg_addr, chg->reg_data); |
| ret = count; |
| } |
| break; |
| case CHG_DATA: |
| if (sscanf(buf, "%x\n", &x) == 1) { |
| data = (u8)x; |
| dev_dbg(dev, "%s: (write) addr = 0x%x, data = 0x%x\n", |
| __func__, chg->reg_addr, data); |
| bq24260_i2c_write(chg->client, |
| chg->reg_addr, &data); |
| ret = count; |
| } |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |