/* * bq51221_charger.c * Samsung bq51221 Charger Driver * * Copyright (C) 2014 Samsung Electronics * Yeongmi Ha * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ENABLE 1 #define DISABLE 0 static enum power_supply_property sec_charger_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL, }; static int bq51221_read_device(struct i2c_client *client, u8 reg, u8 bytes, void *dest) { int ret; if (bytes > 1) { ret = i2c_smbus_read_i2c_block_data(client, reg, bytes, dest); } else { ret = i2c_smbus_read_byte_data(client, reg); if (ret < 0) return ret; *(unsigned char *)dest = (unsigned char)ret; } return ret; } static int bq51221_reg_read(struct i2c_client *client, u8 reg) { struct bq51221_charger_data *charger = i2c_get_clientdata(client); u8 data; int ret = 0; mutex_lock(&charger->io_lock); ret = bq51221_read_device(client, reg, 1, &data); mutex_unlock(&charger->io_lock); if (ret < 0) { pr_err("%s: can't read reg(0x%x), ret(%d)\n", __func__, reg, ret); return ret; } else { pr_err("%s: can read reg(0x%x), ret(%d)\n", __func__, reg, ret); return (int)data; } } static int bq51221_reg_write(struct i2c_client *client, u8 reg, u8 data) { struct bq51221_charger_data *charger = i2c_get_clientdata(client); int ret = 0; mutex_lock(&charger->io_lock); ret = i2c_smbus_write_byte_data(client, reg, data); mutex_unlock(&charger->io_lock); if (ret < 0) pr_err("%s: can't write reg(0x%x), ret(%d)\n", __func__, reg, ret); return ret; } static int bq51221_get_pad_mode(struct i2c_client *client) { int ret = 0; int retry_cnt =0; struct bq51221_charger_data *charger = i2c_get_clientdata(client); if(charger->pdata->pad_mode != BQ51221_PAD_MODE_NONE) { /* read pad mode PMA = 1, WPC = 0 (Status bit)*/ ret = bq51221_reg_read(client, BQ51221_REG_INDICATOR); if(ret < 0) { while(retry_cnt++ < 3) { msleep(50); pr_info("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret); /* read pad mode PMA = 1, WPC = 0 (Status bit)*/ ret = bq51221_reg_read(client, BQ51221_REG_INDICATOR); if(ret >= 0) break; } } pr_info("%s pad_mode = %d \n", __func__,ret); if(ret >= 0) { ret &= BQ51221_POWER_MODE_MASK; if(ret == 0) charger->pdata->pad_mode = BQ51221_PAD_MODE_WPC; else if (ret == 1) charger->pdata->pad_mode = BQ51221_PAD_MODE_PMA; else charger->pdata->pad_mode = BQ51221_PAD_MODE_WPC; } else ret = 0; } return ret; } int bq51221_set_full_charge_info(struct i2c_client *client) { int data = 0; int ret = 0, i = 0; int retry_cnt =0; pr_info("%s\n", __func__); for(i=0; i< 3; i++) { /* send cs100 */ ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_CS100); ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_CS100_VALUE); if(ret < 0) { while(retry_cnt++ < 3) { msleep(50); pr_info("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret); /* send cs100 */ ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_CS100); ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_CS100_VALUE); if(ret >= 0) break; } return ret; } /* send end packet */ data = bq51221_reg_read(client, BQ51221_REG_MAILBOX); data &= !BQ51221_SEND_USER_PKT_DONE_MASK; ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data); /* check packet error */ data = bq51221_reg_read(client, BQ51221_REG_MAILBOX); data &= BQ51221_SEND_USER_PKT_ERR_MASK; data = data >> 5; pr_info("%s error pkt = 0x%x \n",__func__, data); if(data == BQ51221_PTK_ERR_NO_ERR) { pr_err("%s sent CS100!\n",__func__); ret = 1; } else { pr_err("%s can not send CS100! err pkt = 0x%x\n",__func__, data); ret = -1; } msleep(300); } return ret; } int bq51221_set_voreg(struct i2c_client *client, int default_value) { u8 data = 0; int ret = 0; int retry_cnt =0; union power_supply_propval value; struct bq51221_charger_data *charger = i2c_get_clientdata(client); #if defined(CONFIG_WIRELESS_CHARGER_INBATTERY_5V_FIX) return 0; #endif if (charger->pdata->pad_mode == BQ51221_PAD_MODE_PMA) { pr_info("%s PMA MODE, do not set Voreg \n", __func__); return 0; } psy_do_property("battery", get, POWER_SUPPLY_PROP_CAPACITY, value); if ((value.intval >= charger->pdata->wireless_cc_cv) && !default_value) default_value = 1; if (default_value) { /* init VOREG with default value */ ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x01); if(ret < 0) { while(retry_cnt++ < 3) { msleep(50); pr_debug("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret); /* init VOREG with default value */ ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x01); data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER); if(ret >= 0) { pr_debug("%s VOREG = 0x%x \n", __func__, data); break; } } } data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER); pr_info("%s VOREG = 0x%x 5.0V, cnt(%d)\n", __func__, data, retry_cnt); } else { ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x02); if(ret < 0) { while(retry_cnt++ < 3) { msleep(50); pr_debug("%s retry_cnt = %d, ret =%d \n",__func__, retry_cnt, ret); /* init VOREG with default value */ ret = bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x02); data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER); if(ret >= 0) { pr_debug("%s VOREG = 0x%x \n", __func__, data); break; } } } data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER); pr_info("%s VOREG = 0x%x 5.5V, cnt(%d)\n", __func__, data, retry_cnt); } return ret; } int bq51221_set_end_power_transfer(struct i2c_client *client, int ept_mode) { int pad_mode = 0; int data = 0; int ret = 0; pr_info("%s\n", __func__); switch(ept_mode) { case END_POWER_TRANSFER_CODE_OVER_TEMPERATURE: /* read pad mode PMA = 1, WPC = 0 (Status bit)*/ pad_mode = bq51221_reg_read(client, BQ51221_REG_INDICATOR); pr_info("%s pad_mode = %d \n", __func__,pad_mode); if(pad_mode > 0) pad_mode &= BQ51221_POWER_MODE_MASK; if(pad_mode) { pr_info("%s PMA MODE, send EOC \n", __func__); data = bq51221_reg_read(client, BQ51221_REG_MAILBOX); data |= BQ51221_SEND_EOC_MASK; ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data); } else { pr_info("%s WPC MODE, send EPT-OT \n", __func__); ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_EPT); ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_EPT_CODE_OVER_TEMPERATURE); /* send end packet */ data = bq51221_reg_read(client, BQ51221_REG_MAILBOX); data &= !BQ51221_SEND_USER_PKT_DONE_MASK; ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data); /* check packet error */ data = bq51221_reg_read(client, BQ51221_REG_MAILBOX); data &= BQ51221_SEND_USER_PKT_ERR_MASK; data = data >> 5; pr_info("%s error pkt = 0x%x \n",__func__, data); if(data != BQ51221_PTK_ERR_NO_ERR) { pr_err("%s can not send ept! err pkt = 0x%x\n",__func__, data); ret = -1; } } break; case END_POWER_TRANSFER_CODE_RECONFIGURE: pr_info("%s send EPT-Reconfigure \n", __func__); ret = bq51221_reg_write(client, BQ51221_REG_USER_HEADER, BQ51221_EPT_HEADER_EPT); ret = bq51221_reg_write(client, BQ51221_REG_PROP_PACKET_PAYLOAD, BQ51221_EPT_CODE_RECONFIGURE); /* send end packet */ data = bq51221_reg_read(client, BQ51221_REG_MAILBOX); data &= !BQ51221_SEND_USER_PKT_DONE_MASK; ret = bq51221_reg_write(client, BQ51221_REG_MAILBOX, data); /* check packet error */ data = bq51221_reg_read(client, BQ51221_REG_MAILBOX); data &= BQ51221_SEND_USER_PKT_ERR_MASK; data = data >> 5; pr_info("%s error pkt = 0x%x \n",__func__, data); if(data != BQ51221_PTK_ERR_NO_ERR) { pr_err("%s can not send ept! err pkt = 0x%x\n",__func__, data); ret = -1; } break; default: pr_info("%s this ept mode is not reserved \n",__func__); ret = -1; break; } return ret; } void bq51221_wireless_chg_init(struct i2c_client *client) { int data = 0; union power_supply_propval value; struct bq51221_charger_data *charger = i2c_get_clientdata(client); pr_info("%s\n", __func__); psy_do_property("battery", get, POWER_SUPPLY_PROP_CAPACITY, value); /* init I limit(IOREG) */ bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER2, BQ51221_IOREG_100_VALUE); data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER2); pr_info("%s IOREG = 0x%x \n", __func__, data); /* init CEP timing */ /* init RCVD PWR */ /* read pad mode */ bq51221_get_pad_mode(client); pr_info("%s siop = %d \n" ,__func__, charger->pdata->siop_level ); if ((value.intval < charger->pdata->wireless_cc_cv) && (charger->pdata->pad_mode == BQ51221_PAD_MODE_WPC) && (charger->pdata->siop_level == 100) && !charger->pdata->default_voreg) { /* set VOREG set 5.5V*/ bq51221_set_voreg(charger->client, 0); } else { /* init VOREG with default value */ bq51221_set_voreg(charger->client, 1); } } static void bq51221_detect_work( struct work_struct *work) { struct bq51221_charger_data *charger = container_of(work, struct bq51221_charger_data, wpc_work.work); pr_info("%s\n", __func__); bq51221_wireless_chg_init(charger->client); } static int bq51221_chg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct bq51221_charger_data *charger = container_of(psy, struct bq51221_charger_data, psy_chg); switch (psp) { case POWER_SUPPLY_PROP_STATUS: pr_info("%s charger->pdata->cs100_status %d \n",__func__,charger->pdata->cs100_status); val->intval = charger->pdata->cs100_status; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: val->intval = bq51221_get_pad_mode(charger->client); break; case POWER_SUPPLY_PROP_HEALTH: val->intval = 1; break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: val->intval = charger->pdata->siop_level; break; case POWER_SUPPLY_PROP_ONLINE: case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: break; default: return -EINVAL; } return 0; } static int bq51221_chg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct bq51221_charger_data *charger = container_of(psy, struct bq51221_charger_data, psy_chg); union power_supply_propval value; switch (psp) { case POWER_SUPPLY_PROP_STATUS: if(val->intval == POWER_SUPPLY_STATUS_FULL) { charger->pdata->cs100_status = bq51221_set_full_charge_info(charger->client); pr_info("%s charger->pdata->cs100_status %d \n",__func__,charger->pdata->cs100_status); } break; case POWER_SUPPLY_PROP_CHARGE_TYPE: if (!charger->pdata->default_voreg && !delayed_work_pending(&charger->wpc_work)) bq51221_set_voreg(charger->client, val->intval); break; case POWER_SUPPLY_PROP_HEALTH: if(val->intval == POWER_SUPPLY_HEALTH_OVERHEAT || val->intval == POWER_SUPPLY_HEALTH_OVERHEATLIMIT || val->intval == POWER_SUPPLY_HEALTH_COLD) bq51221_set_end_power_transfer(charger->client, END_POWER_TRANSFER_CODE_OVER_TEMPERATURE); else if(val->intval == POWER_SUPPLY_HEALTH_UNDERVOLTAGE) bq51221_set_end_power_transfer(charger->client, END_POWER_TRANSFER_CODE_RECONFIGURE); break; case POWER_SUPPLY_PROP_ONLINE: if(val->intval == POWER_SUPPLY_TYPE_WIRELESS) { charger->pdata->pad_mode = BQ51221_PAD_MODE_WPC; queue_delayed_work(charger->wqueue, &charger->wpc_work, msecs_to_jiffies(5000)); wake_lock_timeout(&charger->wpc_wake_lock, HZ * 6); } else if(val->intval == POWER_SUPPLY_TYPE_BATTERY) { bq51221_set_voreg(charger->client, 1); charger->pdata->pad_mode = BQ51221_PAD_MODE_NONE; cancel_delayed_work(&charger->wpc_work); } break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: charger->pdata->siop_level = val->intval; pr_info("%s siop = %d \n",__func__, charger->pdata->siop_level); break; case POWER_SUPPLY_PROP_CHARGE_OTG_CONTROL: if (val->intval) { charger->pdata->default_voreg = true; bq51221_set_voreg(charger->client, val->intval); } else { charger->pdata->default_voreg = false; psy_do_property("battery", get, POWER_SUPPLY_PROP_STATUS, value); if ((value.intval == POWER_SUPPLY_STATUS_CHARGING) && (charger->pdata->pad_mode == BQ51221_PAD_MODE_WPC)) { queue_delayed_work(charger->wqueue, &charger->wpc_work, msecs_to_jiffies(5000)); wake_lock_timeout(&charger->wpc_wake_lock, HZ * 6); } } break; default: return -EINVAL; } return 0; } #if 0 /* this part is for bq51221s */ static void bq51221_chg_isr_work(struct work_struct *work) { //struct bq51221_charger_data *charger = // container_of(work, struct bq51221_charger_data, isr_work.work); pr_info("%s \n",__func__); } static irqreturn_t bq51221_chg_irq_thread(int irq, void *irq_data) { struct bq51221_charger_data *charger = irq_data; pr_info("%s \n",__func__); schedule_delayed_work(&charger->isr_work, 0); return IRQ_HANDLED; } #endif static int bq51221_chg_parse_dt(struct device *dev, bq51221_charger_platform_data_t *pdata) { int ret = 0; struct device_node *np = dev->of_node; if (!np) { pr_info("%s: np NULL\n", __func__); return 1; } np = of_find_node_by_name(NULL, "battery"); if (!np) { pr_err("%s np NULL\n", __func__); } else { ret = of_property_read_u32(np, "battery,wireless_cc_cv", &pdata->wireless_cc_cv); ret = of_property_read_string(np, "battery,wirelss_charger_name", (char const **)&pdata->wireless_charger_name); if (ret) pr_info("%s: Vendor is Empty\n", __func__); } return ret; #if 0 /* this part is for bq51221s */ ret = pdata->irq_gpio = of_get_named_gpio_flags(np, "bq51221-charger,irq-gpio", 0, &irq_gpio_flags); if (ret < 0) { dev_err(dev, "%s : can't get irq-gpio\r\n", __FUNCTION__); return ret; } pr_info("%s irq_gpio = %d \n",__func__, pdata->irq_gpio); pdata->irq_base = gpio_to_irq(pdata->irq_gpio); return 0; #endif } static int bq51221_charger_probe( struct i2c_client *client, const struct i2c_device_id *id) { struct device_node *of_node = client->dev.of_node; struct bq51221_charger_data *charger; bq51221_charger_platform_data_t *pdata = client->dev.platform_data; int ret = 0; dev_info(&client->dev, "%s: bq51221 Charger Driver Loading\n", __func__); if (of_node) { pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { dev_err(&client->dev, "Failed to allocate memory\n"); return -ENOMEM; } ret = bq51221_chg_parse_dt(&client->dev, pdata); if (ret < 0) goto err_parse_dt; } else { pdata = client->dev.platform_data; } charger = kzalloc(sizeof(*charger), GFP_KERNEL); if (charger == NULL) { dev_err(&client->dev, "Memory is not enough.\n"); ret = -ENOMEM; goto err_wpc_nomem; } charger->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(charger->dev, "I2C functionality is not supported.\n"); ret = -ENOSYS; goto err_i2cfunc_not_support; } charger->client = client; charger->pdata = pdata; pr_info("%s: %s\n", __func__, charger->pdata->wireless_charger_name ); /* if board-init had already assigned irq_base (>=0) , no need to allocate it; assign -1 to let this driver allocate resource by itself*/ #if 0 /* this part is for bq51221s */ if (pdata->irq_base < 0) pdata->irq_base = irq_alloc_descs(-1, 0, BQ51221_EVENT_IRQ, 0); if (pdata->irq_base < 0) { pr_err("%s: irq_alloc_descs Fail! ret(%d)\n", __func__, pdata->irq_base); ret = -EINVAL; goto irq_base_err; } else { charger->irq_base = pdata->irq_base; pr_info("%s: irq_base = %d\n", __func__, charger->irq_base); #if (LINUX_VERSION_CODE>=KERNEL_VERSION(3,4,0)) irq_domain_add_legacy(of_node, BQ51221_EVENT_IRQ, charger->irq_base, 0, &irq_domain_simple_ops, NULL); #endif /*(LINUX_VERSION_CODE>=KERNEL_VERSION(3,4,0))*/ } #endif i2c_set_clientdata(client, charger); charger->psy_chg.name = pdata->wireless_charger_name; charger->psy_chg.type = POWER_SUPPLY_TYPE_UNKNOWN; charger->psy_chg.get_property = bq51221_chg_get_property; charger->psy_chg.set_property = bq51221_chg_set_property; charger->psy_chg.properties = sec_charger_props; charger->psy_chg.num_properties = ARRAY_SIZE(sec_charger_props); mutex_init(&charger->io_lock); #if 0 /* this part is for bq51221s */ if (charger->chg_irq) { INIT_DELAYED_WORK( &charger->isr_work, bq51221_chg_isr_work); ret = request_threaded_irq(charger->chg_irq, NULL, bq51221_chg_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "charger-irq", charger); if (ret) { dev_err(&client->dev, "%s: Failed to Reqeust IRQ\n", __func__); goto err_supply_unreg; } ret = enable_irq_wake(charger->chg_irq); if (ret < 0) dev_err(&client->dev, "%s: Failed to Enable Wakeup Source(%d)\n", __func__, ret); } #endif charger->pdata->cs100_status = 0; charger->pdata->pad_mode = BQ51221_PAD_MODE_NONE; charger->pdata->siop_level = 100; charger->pdata->default_voreg = false; ret = power_supply_register(&client->dev, &charger->psy_chg); if (ret) { dev_err(&client->dev, "%s: Failed to Register psy_chg\n", __func__); goto err_supply_unreg; } charger->wqueue = create_workqueue("bq51221_workqueue"); if (!charger->wqueue) { pr_err("%s: Fail to Create Workqueue\n", __func__); goto err_pdata_free; } wake_lock_init(&(charger->wpc_wake_lock), WAKE_LOCK_SUSPEND, "wpc_wakelock"); INIT_DELAYED_WORK(&charger->wpc_work, bq51221_detect_work); dev_info(&client->dev, "%s: bq51221 Charger Driver Loaded\n", __func__); return 0; err_pdata_free: power_supply_unregister(&charger->psy_chg); err_supply_unreg: mutex_destroy(&charger->io_lock); err_i2cfunc_not_support: kfree(charger); err_wpc_nomem: err_parse_dt: kfree(pdata); return ret; } static int bq51221_charger_remove(struct i2c_client *client) { return 0; } #if defined CONFIG_PM static int bq51221_charger_suspend(struct i2c_client *client, pm_message_t state) { return 0; } static int bq51221_charger_resume(struct i2c_client *client) { return 0; } #else #define p9015_charger_suspend NULL #define p9015_charger_resume NULL #endif static void bq51221_charger_shutdown(struct i2c_client *client) { struct bq51221_charger_data *charger = i2c_get_clientdata(client); int data = 0; if(charger->pdata->pad_mode != BQ51221_PAD_MODE_NONE) { /* init VOREG set 5.0V*/ bq51221_reg_write(client, BQ51221_REG_CURRENT_REGISTER, 0x01); data = bq51221_reg_read(client, BQ51221_REG_CURRENT_REGISTER); pr_info("%s VOREG = 0x%x \n", __func__, data); } } static const struct i2c_device_id bq51221_charger_id_table[] = { { "bq51221-charger", 0 }, { }, }; MODULE_DEVICE_TABLE(i2c, bq51221_id_table); #ifdef CONFIG_OF static struct of_device_id bq51221_charger_match_table[] = { { .compatible = "ti,bq51221-charger",}, {}, }; #else #define bq51221_charger_match_table NULL #endif static struct i2c_driver bq51221_charger_driver = { .driver = { .name = "bq51221-charger", .owner = THIS_MODULE, .of_match_table = bq51221_charger_match_table, }, .shutdown = bq51221_charger_shutdown, .suspend = bq51221_charger_suspend, .resume = bq51221_charger_resume, .probe = bq51221_charger_probe, .remove = bq51221_charger_remove, .id_table = bq51221_charger_id_table, }; static int __init bq51221_charger_init(void) { pr_info("%s \n",__func__); return i2c_add_driver(&bq51221_charger_driver); } static void __exit bq51221_charger_exit(void) { pr_info("%s \n",__func__); i2c_del_driver(&bq51221_charger_driver); } module_init(bq51221_charger_init); module_exit(bq51221_charger_exit); MODULE_DESCRIPTION("Samsung bq51221 Charger Driver"); MODULE_AUTHOR("Samsung Electronics"); MODULE_LICENSE("GPL");