mutex_lock(&bq2415x_i2c_mutex);
ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
mutex_unlock(&bq2415x_i2c_mutex);
if (ret < 0) return ret;
return val;
}
/* read value from register, apply mask and right shift it */ staticint bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg,
u8 mask, u8 shift)
{ int ret;
if (shift > 8) return -EINVAL;
ret = bq2415x_i2c_read(bq, reg); if (ret < 0) return ret; return (ret & mask) >> shift;
}
/* read value from register and return one specified bit */ staticint bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit)
{ if (bit > 8) return -EINVAL; return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit);
}
/**** i2c write functions ****/
/* write value to register */ staticint bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val)
{ struct i2c_client *client = to_i2c_client(bq->dev); struct i2c_msg msg[1];
u8 data[2]; int ret;
mutex_lock(&bq2415x_i2c_mutex);
ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
mutex_unlock(&bq2415x_i2c_mutex);
/* i2c_transfer returns number of messages transferred */ if (ret < 0) return ret; elseif (ret != 1) return -EIO;
return0;
}
/* read value from register, change it with mask left shifted and write back */ staticint bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val,
u8 mask, u8 shift)
{ int ret;
if (shift > 8) return -EINVAL;
ret = bq2415x_i2c_read(bq, reg); if (ret < 0) return ret;
ret &= ~mask;
ret |= val << shift;
return bq2415x_i2c_write(bq, reg, ret);
}
/* change only one bit in register */ staticint bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg, bool val, u8 bit)
{ if (bit > 8) return -EINVAL; return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit);
}
/**** global functions ****/
/* exec command function */ staticint bq2415x_exec_command(struct bq2415x_device *bq, enum bq2415x_command command)
{ int ret;
switch (command) { case BQ2415X_TIMER_RESET: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1, BQ2415X_BIT_TMR_RST); case BQ2415X_OTG_STATUS: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
BQ2415X_BIT_OTG); case BQ2415X_STAT_PIN_STATUS: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
BQ2415X_BIT_EN_STAT); case BQ2415X_STAT_PIN_ENABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1,
BQ2415X_BIT_EN_STAT); case BQ2415X_STAT_PIN_DISABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0,
BQ2415X_BIT_EN_STAT); case BQ2415X_CHARGE_STATUS: return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT); case BQ2415X_BOOST_STATUS: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
BQ2415X_BIT_BOOST); case BQ2415X_FAULT_STATUS: return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT);
case BQ2415X_CHARGE_TERMINATION_STATUS: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
BQ2415X_BIT_TE); case BQ2415X_CHARGE_TERMINATION_ENABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_TE); case BQ2415X_CHARGE_TERMINATION_DISABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_TE); case BQ2415X_CHARGER_STATUS:
ret = bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
BQ2415X_BIT_CE); if (ret < 0) return ret; return ret > 0 ? 0 : 1; case BQ2415X_CHARGER_ENABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_CE); case BQ2415X_CHARGER_DISABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_CE); case BQ2415X_HIGH_IMPEDANCE_STATUS: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
BQ2415X_BIT_HZ_MODE); case BQ2415X_HIGH_IMPEDANCE_ENABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_HZ_MODE); case BQ2415X_HIGH_IMPEDANCE_DISABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_HZ_MODE); case BQ2415X_BOOST_MODE_STATUS: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
BQ2415X_BIT_OPA_MODE); case BQ2415X_BOOST_MODE_ENABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 1, BQ2415X_BIT_OPA_MODE); case BQ2415X_BOOST_MODE_DISABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL, 0, BQ2415X_BIT_OPA_MODE);
case BQ2415X_OTG_LEVEL: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
BQ2415X_BIT_OTG_PL); case BQ2415X_OTG_ACTIVATE_HIGH: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_PL); case BQ2415X_OTG_ACTIVATE_LOW: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_PL); case BQ2415X_OTG_PIN_STATUS: return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
BQ2415X_BIT_OTG_EN); case BQ2415X_OTG_PIN_ENABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 1, BQ2415X_BIT_OTG_EN); case BQ2415X_OTG_PIN_DISABLE: return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE, 0, BQ2415X_BIT_OTG_EN);
case BQ2415X_VENDER_CODE: return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER); case BQ2415X_PART_NUMBER: return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
BQ2415X_MASK_PN, BQ2415X_SHIFT_PN); case BQ2415X_REVISION: return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION);
} return -EINVAL;
}
/* detect chip type */ staticenum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq)
{ struct i2c_client *client = to_i2c_client(bq->dev); int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER);
/* detect chip revision */ staticint bq2415x_detect_revision(struct bq2415x_device *bq)
{ int ret = bq2415x_exec_command(bq, BQ2415X_REVISION); int chip = bq2415x_detect_chip(bq);
if (ret < 0 || chip < 0) return -1;
switch (chip) { case BQ24150: case BQ24150A: case BQ24151: case BQ24151A: case BQ24152: if (ret >= 0 && ret <= 3) return ret; return -1; case BQ24153: case BQ24153A: case BQ24156: case BQ24156A: case BQ24157S: case BQ24158: if (ret == 3) return0; elseif (ret == 1) return1; return -1; case BQ24155: if (ret == 3) return3; return -1; case BQUNKNOWN: return -1;
}
/* get weak battery voltage in mV */ staticint bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq)
{ int ret;
ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL,
BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV); if (ret < 0) return ret; return100 * (34 + ret);
}
/* set battery regulation voltage in mV */ staticint bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq, int mV)
{ int val = (mV/10 - 350) / 2;
/* *Accordingtodatasheet,maximumbatteryregulationvoltageis *4440mVwhichisb101111=47.
*/ if (val < 0)
val = 0; elseif (val > 47) return -EINVAL;
/* get battery regulation voltage in mV */ staticint bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq)
{ int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE,
BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
/* set charge current in mA (platform data must provide resistor sense) */ staticint bq2415x_set_charge_current(struct bq2415x_device *bq, int mA)
{ int val;
if (bq->init_data.resistor_sense <= 0) return -EINVAL;
val = (mA * bq->init_data.resistor_sense - 37400) / 6800; if (val < 0)
val = 0; elseif (val > 7)
val = 7;
/* get charge current in mA (platform data must provide resistor sense) */ staticint bq2415x_get_charge_current(struct bq2415x_device *bq)
{ int ret;
if (bq->init_data.resistor_sense <= 0) return -EINVAL;
ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG); if (ret < 0) return ret; return (37400 + 6800*ret) / bq->init_data.resistor_sense;
}
/* set termination current in mA (platform data must provide resistor sense) */ staticint bq2415x_set_termination_current(struct bq2415x_device *bq, int mA)
{ int val;
if (bq->init_data.resistor_sense <= 0) return -EINVAL;
val = (mA * bq->init_data.resistor_sense - 3400) / 3400; if (val < 0)
val = 0; elseif (val > 7)
val = 7;
/* get termination current in mA (platform data must provide resistor sense) */ staticint bq2415x_get_termination_current(struct bq2415x_device *bq)
{ int ret;
if (bq->init_data.resistor_sense <= 0) return -EINVAL;
ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM); if (ret < 0) return ret; return (3400 + 3400*ret) / bq->init_data.resistor_sense;
}
/* set default value of property */ #define bq2415x_set_default_value(bq, prop) \ do { \ int ret = 0; \ if (bq->init_data.prop != -1) \
ret = bq2415x_set_##prop(bq, bq->init_data.prop); \ if (ret < 0) \ return ret; \
} while (0)
/* set default values of all properties */ staticint bq2415x_set_defaults(struct bq2415x_device *bq)
{
bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_DISABLE);
if (!charger)
ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
if (!boost)
ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
if (ret < 0) return ret;
switch (mode) { case BQ2415X_MODE_OFF:
dev_dbg(bq->dev, "changing mode to: Offline\n");
ret = bq2415x_set_current_limit(bq, 100); break; case BQ2415X_MODE_NONE:
dev_dbg(bq->dev, "changing mode to: N/A\n");
ret = bq2415x_set_current_limit(bq, 100); break; case BQ2415X_MODE_HOST_CHARGER:
dev_dbg(bq->dev, "changing mode to: Host/HUB charger\n");
ret = bq2415x_set_current_limit(bq, 500); break; case BQ2415X_MODE_DEDICATED_CHARGER:
dev_dbg(bq->dev, "changing mode to: Dedicated charger\n");
ret = bq2415x_set_current_limit(bq, 1800); break; case BQ2415X_MODE_BOOST: /* Boost mode */
dev_dbg(bq->dev, "changing mode to: Boost\n");
ret = bq2415x_set_current_limit(bq, 100); break;
}
if (ret < 0) return ret;
if (charger)
ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE); elseif (boost)
ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_ENABLE);
if (val != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK;
/* Ignore event if it was not send by notify_node/notify_device */ if (bq->notify_node) { if (!psy->dev.parent ||
psy->dev.parent->of_node != bq->notify_node) return NOTIFY_OK;
} elseif (bq->init_data.notify_device) { if (strcmp(psy->desc->name, bq->init_data.notify_device) != 0) return NOTIFY_OK;
}
dev_dbg(bq->dev, "notifier call was called\n");
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX,
&prop); if (ret != 0) return NOTIFY_OK;
if (!bq2415x_update_reported_mode(bq, prop.intval)) return NOTIFY_OK;
power_supply_changed(bq->charger);
/* if automode is not enabled do not tell about reported_mode */ if (bq->automode < 1) return NOTIFY_OK;
mod_delayed_work(system_wq, &bq->work, 0);
return NOTIFY_OK;
}
/**** timer functions ****/
/* enable/disable auto resetting chip timer */ staticvoid bq2415x_set_autotimer(struct bq2415x_device *bq, int state)
{
mutex_lock(&bq2415x_timer_mutex);
if (bq->autotimer == state) {
mutex_unlock(&bq2415x_timer_mutex); return;
}
/* called by bq2415x_timer_work on timer error */ staticvoid bq2415x_timer_error(struct bq2415x_device *bq, constchar *msg)
{
bq->timer_error = msg;
sysfs_notify(&bq->charger->dev.kobj, NULL, "timer");
dev_err(bq->dev, "%s\n", msg); if (bq->automode > 0)
bq->automode = 0;
bq2415x_set_mode(bq, BQ2415X_MODE_OFF);
bq2415x_set_autotimer(bq, 0);
}
/* delayed work function for auto resetting chip timer */ staticvoid bq2415x_timer_work(struct work_struct *work)
{ struct bq2415x_device *bq = container_of(work, struct bq2415x_device,
work.work); int ret; int error; int boost; int charge;
if (boost) { switch (error) { /* Non fatal errors, chip is OK */ case0: /* No error */ break; case6: /* Timer expired */
dev_err(bq->dev, "Timer expired\n"); break; case3: /* Battery voltage too low */
dev_err(bq->dev, "Battery voltage to low\n"); break;
if (bq->automode > 0)
ret += sysfs_emit_at(buf, ret, "auto (");
switch (bq->mode) { case BQ2415X_MODE_OFF:
ret += sysfs_emit_at(buf, ret, "off"); break; case BQ2415X_MODE_NONE:
ret += sysfs_emit_at(buf, ret, "none"); break; case BQ2415X_MODE_HOST_CHARGER:
ret += sysfs_emit_at(buf, ret, "host"); break; case BQ2415X_MODE_DEDICATED_CHARGER:
ret += sysfs_emit_at(buf, ret, "dedicated"); break; case BQ2415X_MODE_BOOST:
ret += sysfs_emit_at(buf, ret, "boost"); break;
}
if (bq->automode > 0)
ret += sysfs_emit_at(buf, ret, ")");
ret += sysfs_emit_at(buf, ret, "\n"); return ret;
}
/* show all raw values of chip register, format per line: 'register=value' */ static ssize_t bq2415x_sysfs_show_registers(struct device *dev, struct device_attribute *attr, char *buf)
{ struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy);
ssize_t ret = 0;
ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_STATUS, buf+ret);
ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CONTROL, buf+ret);
ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VOLTAGE, buf+ret);
ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VENDER, buf+ret);
ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CURRENT, buf+ret); return ret;
}
/* set current and voltage limit entries (in mA or mV) */ static ssize_t bq2415x_sysfs_set_limit(struct device *dev, struct device_attribute *attr, constchar *buf,
size_t count)
{ struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); long val; int ret;
if (kstrtol(buf, 10, &val) < 0) return -EINVAL;
if (strcmp(attr->attr.name, "current_limit") == 0)
ret = bq2415x_set_current_limit(bq, val); elseif (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
ret = bq2415x_set_weak_battery_voltage(bq, val); elseif (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
ret = bq2415x_set_battery_regulation_voltage(bq, val); elseif (strcmp(attr->attr.name, "charge_current") == 0)
ret = bq2415x_set_charge_current(bq, val); elseif (strcmp(attr->attr.name, "termination_current") == 0)
ret = bq2415x_set_termination_current(bq, val); else return -EINVAL;
if (ret < 0) return ret; return count;
}
/* show current and voltage limit entries (in mA or mV) */ static ssize_t bq2415x_sysfs_show_limit(struct device *dev, struct device_attribute *attr, char *buf)
{ struct power_supply *psy = dev_to_psy(dev); struct bq2415x_device *bq = power_supply_get_drvdata(psy); int ret;
if (strcmp(attr->attr.name, "current_limit") == 0)
ret = bq2415x_get_current_limit(bq); elseif (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
ret = bq2415x_get_weak_battery_voltage(bq); elseif (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
ret = bq2415x_get_battery_regulation_voltage(bq); elseif (strcmp(attr->attr.name, "charge_current") == 0)
ret = bq2415x_get_charge_current(bq); elseif (strcmp(attr->attr.name, "termination_current") == 0)
ret = bq2415x_get_termination_current(bq); else return -EINVAL;
/* main bq2415x probe function */ staticint bq2415x_probe(struct i2c_client *client)
{ conststruct i2c_device_id *id = i2c_client_get_device_id(client); int ret; int num; char *name = NULL; struct bq2415x_device *bq; struct device_node *np = client->dev.of_node; struct bq2415x_platform_data *pdata = client->dev.platform_data; conststruct acpi_device_id *acpi_id = NULL; struct power_supply *notify_psy = NULL; union power_supply_propval prop;
if (!np && !pdata && !ACPI_HANDLE(&client->dev)) {
dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n"); return -ENODEV;
}
/* Get new ID for the new device */
mutex_lock(&bq2415x_id_mutex);
num = idr_alloc(&bq2415x_id, client, 0, 0, GFP_KERNEL);
mutex_unlock(&bq2415x_id_mutex); if (num < 0) return num;
if (id) {
name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
} elseif (ACPI_HANDLE(&client->dev)) {
acpi_id =
acpi_match_device(client->dev.driver->acpi_match_table,
&client->dev); if (!acpi_id) {
dev_err(&client->dev, "failed to match device name\n");
ret = -ENODEV; goto error_1;
}
name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num);
} if (!name) {
dev_err(&client->dev, "failed to allocate device name\n");
ret = -ENOMEM; goto error_1;
}
bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL); if (!bq) {
ret = -ENOMEM; goto error_2;
}
if (np || ACPI_HANDLE(bq->dev)) {
ret = device_property_read_u32(bq->dev, "ti,current-limit",
&bq->init_data.current_limit); if (ret) goto error_2;
ret = device_property_read_u32(bq->dev, "ti,weak-battery-voltage",
&bq->init_data.weak_battery_voltage); if (ret) goto error_2;
ret = device_property_read_u32(bq->dev, "ti,battery-regulation-voltage",
&bq->init_data.battery_regulation_voltage); if (ret) goto error_2;
ret = device_property_read_u32(bq->dev, "ti,charge-current",
&bq->init_data.charge_current); if (ret) goto error_2;
ret = device_property_read_u32(bq->dev, "ti,termination-current",
&bq->init_data.termination_current); if (ret) goto error_2;
ret = device_property_read_u32(bq->dev, "ti,resistor-sense",
&bq->init_data.resistor_sense); if (ret) goto error_2; if (np)
bq->notify_node = of_parse_phandle(np, "ti,usb-charger-detection", 0);
} else {
memcpy(&bq->init_data, pdata, sizeof(bq->init_data));
}
bq2415x_reset_chip(bq);
ret = bq2415x_power_supply_init(bq); if (ret) {
dev_err(bq->dev, "failed to register power supply: %d\n", ret); goto error_2;
}
ret = bq2415x_set_defaults(bq); if (ret) {
dev_err(bq->dev, "failed to set default values: %d\n", ret); goto error_3;
}
if (bq->notify_node || bq->init_data.notify_device) {
bq->nb.notifier_call = bq2415x_notifier_call;
ret = power_supply_reg_notifier(&bq->nb); if (ret) {
dev_err(bq->dev, "failed to reg notifier: %d\n", ret); goto error_3;
}
bq->automode = 1;
dev_info(bq->dev, "automode supported, waiting for events\n");
} else {
bq->automode = -1;
dev_info(bq->dev, "automode not supported\n");
}
/* Query for initial reported_mode and set it */ if (bq->nb.notifier_call) { if (np) {
notify_psy = power_supply_get_by_reference(of_fwnode_handle(np), "ti,usb-charger-detection"); if (IS_ERR(notify_psy))
notify_psy = NULL;
} elseif (bq->init_data.notify_device) {
notify_psy = power_supply_get_by_name(
bq->init_data.notify_device);
}
} if (notify_psy) {
ret = power_supply_get_property(notify_psy,
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
power_supply_put(notify_psy);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.