| /* |
| * Driver for the TI zx234502 battery charger. |
| * |
| * Author: Mark A. Greer <mgreer@animalcreek.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/power_supply.h> |
| #include <linux/gpio.h> |
| #include <linux/i2c.h> |
| #include <linux/irq.h> |
| #include <linux/kthread.h> |
| //#include <linux/mutex.h> |
| #include <linux/semaphore.h> |
| |
| #include <linux/power/zx234502_charger.h> |
| #include <linux/mfd/zx234290.h> |
| |
| #include <mach/gpio.h> |
| #include <mach/pcu.h> |
| #include <mach/zx29_usb.h> |
| #include <linux/workqueue.h> |
| |
| #include <linux/slab.h> |
| #include <linux/debugfs.h> |
| #include <asm/uaccess.h> |
| |
| /* |
| * The FAULT register is latched by the zx234502 (except for NTC_FAULT) |
| * so the first read after a fault returns the latched value and subsequent |
| * reads return the current value. In order to return the fault status |
| * to the user, have the interrupt handler save the reg's value and retrieve |
| * it in the appropriate health/status routine. Each routine has its own |
| * flag indicating whether it should use the value stored by the last run |
| * of the interrupt handler or do an actual reg read. That way each routine |
| * can report back whatever fault may have occured. |
| */ |
| struct zx234502_dev_info { |
| struct i2c_client *client; |
| struct device *dev; |
| struct power_supply charger; |
| struct power_supply battery; |
| struct power_supply boost; |
| char model_name[I2C_NAME_SIZE]; |
| kernel_ulong_t model; |
| unsigned int gpio_int; |
| unsigned int irq; |
| struct mutex bs_reg_lock; |
| bool first_time; /*let the first reset do not ask mmi*/ |
| bool charger_health_valid; |
| bool battery_health_valid; |
| bool battery_status_valid; |
| u8 ciis_curr_reg; |
| u8 cbis_curr_reg; |
| u8 pis_curr_reg; |
| u8 cfis_curr_reg; |
| |
| u8 ciis_pre_reg; |
| u8 cbis_pre_reg; |
| u8 pis_pre_reg; |
| u8 cfis_pre_reg; |
| u8 watchdog; |
| u8 boost_online_flag; |
| struct delayed_work boostWorkStruct ; |
| struct workqueue_struct *boostQueue; |
| struct semaphore chgirq_sem; |
| struct task_struct *chg_irq_thread; |
| u16 boostcount; |
| |
| struct zx234502_platform_data *pdata; |
| //unsigned int chg_type; |
| }; |
| |
| //struct zx234502_platform_data *g_platform_data = NULL; |
| struct zx234502_dev_info *g_bdi = NULL; |
| |
| /* REG01[2:0] (ISET_DCIN) in mAh */ |
| static const int zx234502_in_ilimit_values[] = { |
| 150, 450, 850, 1000, 1500, 2000, 2500, 3000 |
| }; |
| /* REG01[6:3] (VINDPM) in mVh */ |
| static const int zx234502_vindpm_values[] = { |
| 3880, 3960, 4040, 4120, |
| 4200,4280, 4360, 4440, |
| 4520,4600, 4680, 4760, |
| 4840,4920, 5000, 5080, |
| }; |
| |
| /* |
| * The tables below provide a 2-way mapping for the value that goes in |
| * the register field and the real-world value that it represents. |
| * The index of the array is the value that goes in the register; the |
| * number at that index in the array is the real-world value that it |
| * represents. |
| */ |
| /* REG02[7:2] (ISETA) in mAh */ |
| static const int zx234502_isc_ichg_values[] = { |
| 512, 576, 640, 704, 768, 832, 896, 960, |
| 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, |
| 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, |
| 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, |
| 2560, 2624, 2688, 2752, 2816, 2880, 2944, 3008, |
| 3072, 3136, 3200, 3264, 3328, 3392, 3456, 3520, |
| 3584, 3648, 3712, 3776, 3840, 3904, 3968, 4032, |
| 4096, 4160, 4224, 4288, 4352, 4416, 4480, 4544 |
| }; |
| |
| /* REG04[3:1] (VSETA) in mV */ |
| static const int zx234502_cvc_vseta_values[] = { |
| 4100, 4150, 4200, 4250, 4300, 4350, 4400, 4450, |
| }; |
| |
| /* REG02[7:5] (VSYS_MIN) in mV */ |
| static const int zx234502_vsc_sysmin_values[] = { |
| 3100, 3200, 3300, 3400, 3500, 3600, 3700,3800 |
| }; |
| static int stopchg_flag = 0; |
| /* |
| * Return the index in 'tbl' of greatest value that is less than or equal to |
| * 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that |
| * the values in 'tbl' are sorted from smallest to largest and 'tbl_size' |
| * is less than 2^8. |
| */ |
| static u8 zx234502_find_idx(const int tbl[], int tbl_size, int v) |
| { |
| int i; |
| |
| for (i = 1; i < tbl_size; i++) |
| if (v < tbl[i]) |
| break; |
| |
| return i - 1; |
| } |
| |
| /* Basic driver I/O routines */ |
| |
| static int zx234502_read(struct zx234502_dev_info *bdi, u8 reg, u8 *data) |
| { |
| int ret; |
| |
| ret = i2c_smbus_read_byte_data(bdi->client, reg); |
| if (ret < 0) |
| return ret; |
| |
| *data = ret; |
| return 0; |
| } |
| |
| static int zx234502_write(struct zx234502_dev_info *bdi, u8 reg, u8 data) |
| { |
| return i2c_smbus_write_byte_data(bdi->client, reg, data); |
| } |
| |
| static int zx234502_read_mask(struct zx234502_dev_info *bdi, u8 reg, |
| u8 mask, u8 shift, u8 *data) |
| { |
| u8 v; |
| int ret; |
| |
| ret = zx234502_read(bdi, reg, &v); |
| if (ret < 0) |
| return ret; |
| |
| v &= mask; |
| v >>= shift; |
| *data = v; |
| |
| return 0; |
| } |
| |
| static int zx234502_write_mask(struct zx234502_dev_info *bdi, u8 reg, |
| u8 mask, u8 shift, u8 data) |
| { |
| u8 v; |
| int ret; |
| |
| ret = zx234502_read(bdi, reg, &v); |
| if (ret < 0) |
| return ret; |
| |
| v &= ~mask; |
| v |= ((data << shift) & mask); |
| |
| return zx234502_write(bdi, reg, v); |
| } |
| |
| static int zx234502_get_field_val(struct zx234502_dev_info *bdi, |
| u8 reg, u8 mask, u8 shift, |
| const int tbl[], int tbl_size, |
| int *val) |
| { |
| u8 v; |
| int ret; |
| |
| ret = zx234502_read_mask(bdi, reg, mask, shift, &v); |
| if (ret < 0) |
| return ret; |
| |
| v = (v >= tbl_size) ? (tbl_size - 1) : v; |
| *val = tbl[v]; |
| |
| return 0; |
| } |
| |
| static int zx234502_set_field_val(struct zx234502_dev_info *bdi, |
| u8 reg, u8 mask, u8 shift, |
| const int tbl[], int tbl_size, |
| int val) |
| { |
| u8 idx; |
| |
| idx = zx234502_find_idx(tbl, tbl_size, val); |
| |
| return zx234502_write_mask(bdi, reg, mask, shift, idx); |
| } |
| |
| //#ifdef CONFIG_SYSFS |
| #if 0 |
| /* |
| * There are a numerous options that are configurable on the zx234502 |
| * that go well beyond what the power_supply properties provide access to. |
| * Provide sysfs access to them so they can be examined and possibly modified |
| * on the fly. They will be provided for the charger power_supply object only |
| * and will be prefixed by 'f_' to make them easier to recognize. |
| */ |
| |
| struct zx234502_sysfs_field_info { |
| struct device_attribute attr; |
| u8 reg; |
| u8 mask; |
| u8 shift; |
| }; |
| |
| |
| #define ZX234502_SYSFS_FIELD(_name, r, f, m, store) \ |
| { \ |
| .attr = __ATTR(f_##_name, m, zx234502_sysfs_show, store), \ |
| .reg = ZX234502_REG_##r, \ |
| .mask = ZX234502_REG_##r##_##f##_MASK, \ |
| .shift = ZX234502_REG_##r##_##f##_SHIFT, \ |
| } |
| |
| #define ZX234502_SYSFS_FIELD_RW(_name, r, f) \ |
| ZX234502_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \ |
| zx234502_sysfs_store) |
| |
| #define ZX234502_SYSFS_FIELD_RO(_name, r, f) \ |
| ZX234502_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL) |
| |
| static ssize_t zx234502_sysfs_show(struct device *dev, |
| struct device_attribute *attr, char *buf); |
| static ssize_t zx234502_sysfs_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count); |
| |
| |
| /* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */ |
| //#undef SS |
| |
| static struct zx234502_sysfs_field_info zx234502_sysfs_field_tbl[] = { |
| /* sysfs name reg field in reg */ |
| ZX234502_SYSFS_FIELD_RW(chg_reset, MS, CHGRST), |
| ZX234502_SYSFS_FIELD_RW(pmuon, MS, PMUON), |
| ZX234502_SYSFS_FIELD_RW(en_ship, MS, ENSHIP), |
| ZX234502_SYSFS_FIELD_RW(en_otg, MS, ENOTG), |
| ZX234502_SYSFS_FIELD_RW(chg_config, MS, ENCHG), |
| ZX234502_SYSFS_FIELD_RW(en_powerbank, MS, PWRBNK), |
| ZX234502_SYSFS_FIELD_RW(insus, MS, INSUS), |
| ZX234502_SYSFS_FIELD_RW(in_limit, ISC, ISET_DCIN), |
| ZX234502_SYSFS_FIELD_RW(vsys_min, VSC, VSYS_MIN), |
| ZX234502_SYSFS_FIELD_RW(ichg, CCC, ISETA), |
| ZX234502_SYSFS_FIELD_RW(force_ichg_50pct, CCC, ICHG_50PCT), |
| ZX234502_SYSFS_FIELD_RW(iprechg, CVC, IPRECHG), |
| ZX234502_SYSFS_FIELD_RW(vbat_full, CVC, VBAT), |
| ZX234502_SYSFS_FIELD_RW(vpre_to_fast, CVC, VBATFC), |
| ZX234502_SYSFS_FIELD_RW(en_timer, CTC, EN_TIMER), |
| ZX234502_SYSFS_FIELD_RW(en_2timer, CTC, EN_2XTIMER), |
| ZX234502_SYSFS_FIELD_RW(stopchg_flag, OTGC, OTGV), |
| ZX234502_SYSFS_FIELD_RW(iotg_limit, OTGC, OTGI_LIM), |
| ZX234502_SYSFS_FIELD_RW(ents_otg, OTGC, ENTS_OTG), |
| ZX234502_SYSFS_FIELD_RO(en_therm, THR, EN_THERM), |
| ZX234502_SYSFS_FIELD_RW(therm_threshold, THR, THERM_THLD), |
| ZX234502_SYSFS_FIELD_RW(en_2det, THR, EN_2DET), |
| ZX234502_SYSFS_FIELD_RW(en_1det, THR, EN_1DET), |
| ZX234502_SYSFS_FIELD_RW(en_jeita, THR, EN_JEITA), |
| ZX234502_SYSFS_FIELD_RW(en_ilimitdj, THR, EN_ILIMITDJ), |
| ZX234502_SYSFS_FIELD_RW(tscold, TS, COLD), |
| ZX234502_SYSFS_FIELD_RW(tscool, TS, COOL), |
| ZX234502_SYSFS_FIELD_RW(tswarm, TS, WARM), |
| ZX234502_SYSFS_FIELD_RW(tshot, TS, HOT), |
| ZX234502_SYSFS_FIELD_RW(ntcdet, PTS,NTCDET), |
| ZX234502_SYSFS_FIELD_RW(int_clear, CIIS, INT), |
| ZX234502_SYSFS_FIELD_RO(ciis_reg, CIIS, ALL), |
| ZX234502_SYSFS_FIELD_RW(mask_chgrun, CIIM, MASK_CHGRUN), |
| ZX234502_SYSFS_FIELD_RW(mask_inlimit, CIIM, MASK_INLIMIT), |
| ZX234502_SYSFS_FIELD_RW(mask_thr, CIIM, MASK_THR), |
| ZX234502_SYSFS_FIELD_RW(mask_ts, CIIM, MASK_TS), |
| ZX234502_SYSFS_FIELD_RW(mask_dcdet, CIIM, MASK_DCDET), |
| ZX234502_SYSFS_FIELD_RO(cbis_reg, CBIS, ALL), |
| ZX234502_SYSFS_FIELD_RW(mask_pg, CBIM, MASK_DCIN_PG), |
| ZX234502_SYSFS_FIELD_RW(mask_batlow, CBIM, MASK_BATLOW), |
| ZX234502_SYSFS_FIELD_RW(mask_nobat, CBIM, MASK_NOBAT), |
| ZX234502_SYSFS_FIELD_RW(mask_eoc, CBIM, MASK_EOC), |
| ZX234502_SYSFS_FIELD_RW(mask_timer, CBIM, MASK_SAFE_TIMER), |
| ZX234502_SYSFS_FIELD_RW(mask_otg_fault, CBIM, MASK_OTG_FAULT), |
| ZX234502_SYSFS_FIELD_RO(pis_reg, PIS, ALL), |
| ZX234502_SYSFS_FIELD_RW(mask_pwron_it, PIM, MASK_POWERON_IT), |
| ZX234502_SYSFS_FIELD_RW(mask_pwron_lp, PIM, MASK_POWERON_LP), |
| ZX234502_SYSFS_FIELD_RO(cfis_reg, CFIS, ALL), |
| ZX234502_SYSFS_FIELD_RO(version_info, VER, INFO), |
| }; |
| |
| static struct attribute * |
| zx234502_sysfs_attrs[ARRAY_SIZE(zx234502_sysfs_field_tbl) + 1]; |
| |
| static const struct attribute_group zx234502_sysfs_attr_group = { |
| .attrs = zx234502_sysfs_attrs, |
| }; |
| |
| static void zx234502_sysfs_init_attrs(void) |
| { |
| int i, limit = ARRAY_SIZE(zx234502_sysfs_field_tbl); |
| |
| for (i = 0; i < limit; i++) |
| zx234502_sysfs_attrs[i] = &zx234502_sysfs_field_tbl[i].attr.attr; |
| |
| zx234502_sysfs_attrs[limit] = NULL; /* Has additional entry for this */ |
| } |
| |
| static struct zx234502_sysfs_field_info *zx234502_sysfs_field_lookup( |
| const char *name) |
| { |
| int i, limit = ARRAY_SIZE(zx234502_sysfs_field_tbl); |
| |
| for (i = 0; i < limit; i++) |
| if (!strcmp(name, zx234502_sysfs_field_tbl[i].attr.attr.name)) |
| break; |
| |
| if (i >= limit) |
| return NULL; |
| |
| return &zx234502_sysfs_field_tbl[i]; |
| } |
| |
| static ssize_t zx234502_sysfs_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct power_supply *psy = dev_get_drvdata(dev); |
| struct zx234502_dev_info *bdi = |
| container_of(psy, struct zx234502_dev_info, charger); |
| struct zx234502_sysfs_field_info *info; |
| int ret; |
| u8 v; |
| |
| info = zx234502_sysfs_field_lookup(attr->attr.name); |
| if (!info) |
| return -EINVAL; |
| |
| ret = zx234502_read_mask(bdi, info->reg, info->mask, info->shift, &v); |
| if (ret) |
| return ret; |
| |
| return scnprintf(buf, PAGE_SIZE, "%hhx\n", v); |
| } |
| |
| static ssize_t zx234502_sysfs_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct power_supply *psy = dev_get_drvdata(dev); |
| struct zx234502_dev_info *bdi = |
| container_of(psy, struct zx234502_dev_info, charger); |
| struct zx234502_sysfs_field_info *info; |
| int ret; |
| u8 v; |
| |
| info = zx234502_sysfs_field_lookup(attr->attr.name); |
| if (!info) |
| return -EINVAL; |
| |
| ret = kstrtou8(buf, 0, &v); |
| if (ret < 0) |
| return ret; |
| |
| ret = zx234502_write_mask(bdi, info->reg, info->mask, info->shift, v); |
| if (ret) |
| return ret; |
| |
| return count; |
| } |
| |
| static int zx234502_sysfs_create_group(struct zx234502_dev_info *bdi) |
| { |
| zx234502_sysfs_init_attrs(); |
| |
| return sysfs_create_group(&bdi->charger.dev->kobj, |
| &zx234502_sysfs_attr_group); |
| } |
| |
| static void zx234502_sysfs_remove_group(struct zx234502_dev_info *bdi) |
| { |
| sysfs_remove_group(&bdi->charger.dev->kobj, &zx234502_sysfs_attr_group); |
| } |
| #else |
| static int zx234502_sysfs_create_group(struct zx234502_dev_info *bdi) |
| { |
| return 0; |
| } |
| |
| static inline void zx234502_sysfs_remove_group(struct zx234502_dev_info *bdi) {} |
| #endif |
| |
| #if 0 |
| /*set the Vsys min*/ |
| static int zx234502_set_vsys_min(struct zx234502_dev_info *bdi,const union power_supply_propval *val) |
| { |
| return zx234502_set_field_val(bdi, ZX234502_REG_VSC, |
| ZX234502_REG_VSC_VSYS_MIN_MASK, ZX234502_REG_VSC_VSYS_MIN_SHIFT, |
| zx234502_cvc_vseta_values, |
| ARRAY_SIZE(zx234502_cvc_vseta_values), val->intval); |
| //return 0 |
| } |
| #endif |
| static int zx234502_register_reset(struct zx234502_dev_info *bdi) |
| { |
| int ret, limit = 100; |
| u8 v; |
| |
| /* Reset the registers */ |
| ret = zx234502_write_mask(bdi, ZX234502_REG_MS,ZX234502_REG_MS_CHGRST_MASK,ZX234502_REG_MS_CHGRST_SHIFT,0x1); |
| if (ret < 0) |
| return ret; |
| |
| /* Reset bit will be cleared by hardware so poll until it is */ |
| do |
| { |
| ret = zx234502_read_mask(bdi, ZX234502_REG_MS,ZX234502_REG_MS_CHGRST_MASK,ZX234502_REG_MS_CHGRST_SHIFT,&v); |
| if (ret < 0) |
| return ret; |
| |
| if (!v) |
| break; |
| |
| udelay(10); |
| } while (--limit); |
| |
| if (!limit) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| |
| /* Charger power supply property routines */ |
| |
| static int zx234502_charger_get_charge_type(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| val->intval = bdi->charger.type; |
| return 0; |
| } |
| |
| static int zx234502_charger_get_status(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| u8 cbis_reg=0,ms_reg=0; |
| int status=POWER_SUPPLY_STATUS_UNKNOWN; |
| int ret=0; |
| |
| mutex_lock(&bdi->bs_reg_lock); |
| |
| if (bdi->battery_status_valid) { |
| cbis_reg = bdi->cbis_curr_reg; |
| bdi->battery_status_valid = false; |
| mutex_unlock(&bdi->bs_reg_lock); |
| } else { |
| mutex_unlock(&bdi->bs_reg_lock); |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CBIS, &cbis_reg); |
| if (ret < 0) |
| return ret; |
| } |
| |
| ret = zx234502_read(bdi, ZX234502_REG_MS, &ms_reg); |
| if (ret < 0) |
| return ret; |
| |
| /* |
| * The battery must be discharging when any of these are true: |
| * - there is no good power source; |
| * - there is a charge fault. |
| * Could also be discharging when in "supplement mode" but |
| * there is no way to tell when its in that mode. |
| */ |
| if (!(cbis_reg & ZX234502_REG_CBIS_DCIN_PG_MASK )||(cbis_reg & ZX234502_REG_CBIS_NOBAT_MASK )) { |
| status = POWER_SUPPLY_STATUS_DISCHARGING; |
| } |
| else if (ms_reg & ZX234502_REG_MS_ENCHG_MASK){ |
| status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| } |
| // disable or full |
| else if(ms_reg & ZX234502_REG_MS_INSUS_MASK) |
| { |
| if (((cbis_reg & ZX234502_REG_CBIS_CHGSTAT_MASK) >> ZX234502_REG_CBIS_CHGSTAT_SHIFT)== 3) |
| { |
| status = POWER_SUPPLY_STATUS_FULL; |
| } |
| else |
| status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| } |
| else { |
| cbis_reg &= ZX234502_REG_CBIS_CHGSTAT_MASK; |
| cbis_reg >>= ZX234502_REG_CBIS_CHGSTAT_SHIFT; |
| |
| switch (cbis_reg) { |
| case 0x0: /* Charging */ |
| case 0x2: /* ReCharging frome THERM */ |
| status = POWER_SUPPLY_STATUS_CHARGING; |
| break; |
| case 0x3: /* Charge Termination Done */ |
| status = POWER_SUPPLY_STATUS_FULL; |
| break; |
| default: |
| ret = -EIO; |
| } |
| } |
| |
| if (!ret) |
| val->intval = status; |
| |
| return ret; |
| } |
| |
| static int zx234502_charger_get_health(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| u8 ciis_reg=0, cbis_reg=0, cfis_reg=0; |
| int health=0, ret=0; |
| |
| mutex_lock(&bdi->bs_reg_lock); |
| |
| if (bdi->charger_health_valid) { |
| bdi->charger_health_valid = false; |
| cbis_reg = bdi->cbis_curr_reg; |
| ciis_reg = bdi->ciis_curr_reg; |
| cfis_reg = bdi->cfis_curr_reg; |
| mutex_unlock(&bdi->bs_reg_lock); |
| } else { |
| mutex_unlock(&bdi->bs_reg_lock); |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CIIS, &ciis_reg); |
| if (ret < 0) |
| return ret; |
| ret = zx234502_read(bdi, ZX234502_REG_CBIS, &cbis_reg); |
| if (ret < 0) |
| return ret; |
| ret = zx234502_read(bdi, ZX234502_REG_CFIS, &cfis_reg); |
| if (ret < 0) |
| return ret; |
| |
| printk(KERN_INFO "zx234502_charger_get_health REG_0xa=0x%x,REG_0xc=0x%x,REG_0x10=0x%x.\n",ciis_reg,cbis_reg,cfis_reg); |
| |
| } |
| |
| if (cbis_reg & ZX234502_REG_CBIS_OTG_FAULT_MASK) { |
| /* |
| * This could be over-current or over-voltage but there's |
| * no way to tell which. Return 'OVERVOLTAGE' since there |
| * isn't an 'OVERCURRENT' value defined that we can return |
| * even if it was over-current. |
| */ |
| health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| } |
| else if(cbis_reg & ZX234502_REG_CBIS_SAFE_TIMER_MASK) { |
| health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; |
| } |
| else if(cbis_reg & ZX234502_REG_CBIS_DCIN_PG_MASK) { |
| health = POWER_SUPPLY_HEALTH_GOOD; |
| } |
| else if(ciis_reg & (ZX234502_REG_CIIS_INLIMIT_MASK|ZX234502_REG_CIIS_THERM_MASK)) { |
| health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
| } |
| else if(cfis_reg & ZX234502_REG_CFIS_THSD_MASK){ |
| health = POWER_SUPPLY_HEALTH_OVERHEAT; /*the chip hot error*/ |
| } |
| else{ |
| health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| } |
| |
| val->intval = health; |
| |
| return 0; |
| } |
| |
| int zx234502_charger_get_online(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| u8 v; |
| int ret; |
| |
| ret = zx234502_read_mask(bdi, ZX234502_REG_CBIS,ZX234502_REG_CBIS_DCIN_PG_MASK, |
| ZX234502_REG_CBIS_DCIN_PG_SHIFT, &v); |
| if (ret < 0) |
| return ret; |
| |
| val->intval = v; |
| return 0; |
| }EXPORT_SYMBOL (zx234502_charger_get_online); |
| #if 0 |
| static int zx234502_charger_get_current(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| int curr, ret; |
| |
| ret = zx234502_get_field_val(bdi, ZX234502_REG_ISC, |
| ZX234502_REG_ISC_ISET_DCIN_MASK, ZX234502_REG_ISC_ISET_DCIN_SHIFT, |
| zx234502_in_ilimit_values, |
| ARRAY_SIZE(zx234502_in_ilimit_values), &curr); |
| if (ret < 0) |
| return ret; |
| |
| val->intval = curr; |
| return 0; |
| } |
| |
| static int zx234502_charger_get_current_max(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| int idx = ARRAY_SIZE(zx234502_in_ilimit_values) - 1; |
| |
| val->intval = zx234502_in_ilimit_values[idx]; |
| return 0; |
| } |
| |
| static int zx234502_charger_get_voltage(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| #if 0 |
| int voltage, ret; |
| ret = zx234502_get_field_val(bdi, ZX234502_REG_CVC, |
| ZX234502_REG_CVC_VBAT_MASK, ZX234502_REG_CVC_VBAT_SHIFT, |
| zx234502_cvc_vseta_values, |
| ARRAY_SIZE(zx234502_cvc_vseta_values), &voltage); |
| if (ret < 0) |
| return ret; |
| |
| val->intval = voltage; |
| #endif |
| return 0; |
| } |
| #endif |
| |
| static int zx234502_charger_get_charger_enabled(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| u8 charger_enabled; |
| int ret; |
| |
| ret = zx234502_read_mask(bdi, ZX234502_REG_MS, |
| ZX234502_REG_MS_INSUS_MASK, |
| ZX234502_REG_MS_INSUS_SHIFT, &charger_enabled); |
| if (ret < 0) |
| return ret; |
| |
| val->intval = !charger_enabled; |
| return 0; |
| } |
| static int zx234502_charger_get_voltage_max(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| int voltage, ret; |
| |
| ret = zx234502_get_field_val(bdi, ZX234502_REG_CVC, |
| ZX234502_REG_CVC_VBAT_MASK, ZX234502_REG_CVC_VBAT_SHIFT, |
| zx234502_cvc_vseta_values, |
| ARRAY_SIZE(zx234502_cvc_vseta_values), &voltage); |
| if (ret < 0) |
| return ret; |
| |
| val->intval = voltage; |
| return 0; |
| |
| } |
| #if 0 |
| static int zx234502_charger_set_current(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| return zx234502_set_field_val(bdi, ZX234502_REG_ISC, |
| ZX234502_REG_ISC_ISET_DCIN_MASK, ZX234502_REG_ISC_ISET_DCIN_MASK, |
| zx234502_in_ilimit_values, |
| ARRAY_SIZE(zx234502_in_ilimit_values), val->intval); |
| |
| } |
| #endif |
| static int zx234502_charger_set_voltage(struct zx234502_dev_info *bdi,const union power_supply_propval *val) |
| { |
| return zx234502_set_field_val(bdi, ZX234502_REG_CVC, |
| ZX234502_REG_CVC_VBAT_MASK, ZX234502_REG_CVC_VBAT_SHIFT, |
| zx234502_cvc_vseta_values, |
| ARRAY_SIZE(zx234502_cvc_vseta_values), val->intval); |
| } |
| |
| static int zx234502_charger_set_charger_config(struct zx234502_dev_info *bdi,const union power_supply_propval *val) |
| { |
| int ret; |
| union power_supply_propval enable_charge; |
| |
| if (val->intval){ |
| enable_charge.intval = 0 ; |
| stopchg_flag = 1; |
| ret = zx234502_write_mask(bdi, ZX234502_REG_OTGC, |
| ZX234502_REG_OTGC_OTGV_MASK, |
| ZX234502_REG_OTGC_OTGV_SHIFT,1); |
| printk("mmi start chg\n"); |
| } |
| else{ |
| stopchg_flag = 2; |
| enable_charge.intval = 1 ; |
| ret = zx234502_write_mask(bdi, ZX234502_REG_OTGC, |
| ZX234502_REG_OTGC_OTGV_MASK, |
| ZX234502_REG_OTGC_OTGV_SHIFT,2); |
| printk("mmi stop chg\n"); |
| } |
| |
| ret = zx234502_write_mask(bdi, ZX234502_REG_MS, |
| ZX234502_REG_MS_INSUS_MASK, |
| ZX234502_REG_MS_INSUS_SHIFT, enable_charge.intval ); /*0:disable 1:enable*/ |
| |
| return ret; |
| } |
| |
| |
| static int zx234502_charger_get_property(struct power_supply *psy,enum power_supply_property psp, union power_supply_propval *val) |
| { |
| struct zx234502_dev_info *bdi = container_of(psy, struct zx234502_dev_info, charger); |
| int ret; |
| |
| //dev_dbg(bdi->dev, "prop: %d\n", psp); |
| |
| //pm_runtime_get_sync(bdi->dev); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_PC1_AC2: |
| ret = zx234502_charger_get_charge_type(bdi, val); |
| break; |
| |
| case POWER_SUPPLY_PROP_STATUS: |
| ret = zx234502_charger_get_status(bdi, val); |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| ret = zx234502_charger_get_health(bdi, val); |
| break; |
| case POWER_SUPPLY_PROP_ONLINE: |
| ret = zx234502_charger_get_online(bdi, val); |
| break; |
| |
| /* |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| ret = zx234502_charger_get_current(bdi, val); |
| break; |
| |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| ret = zx234502_charger_get_current_max(bdi, val); |
| break; |
| |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| ret = zx234502_charger_get_voltage(bdi, val); |
| break; |
| */ |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| ret = zx234502_charger_get_voltage_max(bdi, val); |
| break; |
| |
| case POWER_SUPPLY_PROP_CHARGE_ENABLED: |
| ret = zx234502_charger_get_charger_enabled(bdi, val); |
| break; |
| default: |
| ret = -ENODATA; |
| } |
| |
| //pm_runtime_put_sync(bdi->dev); |
| return ret; |
| } |
| |
| static int zx234502_charger_set_property(struct power_supply *psy,enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct zx234502_dev_info *bdi = |
| container_of(psy, struct zx234502_dev_info, charger); |
| int ret; |
| |
| //dev_dbg(bdi->dev, "prop: %d\n", psp); |
| |
| //pm_runtime_get_sync(bdi->dev); |
| |
| switch (psp) { |
| #if 0 |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| ret = zx234502_charger_set_current(bdi, val); |
| break; |
| #endif |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| ret = zx234502_charger_set_voltage(bdi, val); |
| break; |
| |
| case POWER_SUPPLY_PROP_CHARGE_ENABLED: |
| ret = zx234502_charger_set_charger_config(bdi, val); |
| power_supply_changed(&bdi->charger); |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| |
| //pm_runtime_put_sync(bdi->dev); |
| return ret; |
| } |
| |
| static int zx234502_charger_property_is_writeable(struct power_supply *psy,enum power_supply_property psp) |
| { |
| int ret; |
| |
| switch (psp) |
| { |
| //case POWER_SUPPLY_PROP_CURRENT_NOW: |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| case POWER_SUPPLY_PROP_CHARGE_ENABLED: |
| ret = 1; |
| break; |
| default: |
| ret = 0; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static enum power_supply_property zx234502_charger_properties[] = { |
| POWER_SUPPLY_PROP_PC1_AC2, |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_ONLINE, |
| //POWER_SUPPLY_PROP_CURRENT_NOW, |
| //POWER_SUPPLY_PROP_CURRENT_MAX, |
| //POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX, |
| POWER_SUPPLY_PROP_CHARGE_ENABLED, |
| }; |
| |
| static char *zx234502_charger_supplied_to[] = { |
| "main-battery", |
| }; |
| |
| static void zx234502_charger_init(struct power_supply *charger) |
| { |
| charger->name = "charger"; |
| charger->type = POWER_SUPPLY_PCAC_UNKNOWN; |
| charger->properties = zx234502_charger_properties; |
| charger->num_properties = ARRAY_SIZE(zx234502_charger_properties); |
| charger->supplied_to = zx234502_charger_supplied_to; |
| //charger->num_supplies = ARRAY_SIZE(zx234502_charger_supplied_to); |
| charger->get_property = zx234502_charger_get_property; |
| charger->set_property = zx234502_charger_set_property; |
| charger->property_is_writeable = zx234502_charger_property_is_writeable; |
| } |
| |
| /* Battery power supply property routines */ |
| |
| static int zx234502_battery_get_health(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| u8 v; |
| int health, ret; |
| |
| mutex_lock(&bdi->bs_reg_lock); |
| |
| if(true!=bdi->pdata->ts_flag){ |
| val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; |
| mutex_unlock(&bdi->bs_reg_lock); |
| return 0; |
| } |
| |
| if (bdi->battery_health_valid) { |
| v = bdi->ciis_curr_reg; |
| bdi->battery_health_valid = false; |
| } else { |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CIIS, &v); |
| if (ret < 0){ |
| |
| mutex_unlock(&bdi->bs_reg_lock); |
| return ret; |
| } |
| } |
| |
| if (v & ZX234502_REG_CIIS_THERM_MASK) { |
| health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| } else { |
| v &= ZX234502_REG_CIIS_TS_METER_MASK; |
| v >>= ZX234502_REG_CIIS_TS_METER_SHIFT; |
| |
| switch (v) { |
| case ZX234502_REG_CISS_TS_NORMAL: /* Normal */ |
| health = POWER_SUPPLY_HEALTH_GOOD; |
| break; |
| case ZX234502_REG_CISS_TS_WARM: /*warm*/ |
| health = POWER_SUPPLY_HEALTH_WARM; |
| break; |
| case ZX234502_REG_CISS_TS_HOT: /* TS1 Hot */ |
| health = POWER_SUPPLY_HEALTH_OVERHEAT; |
| break; |
| case ZX234502_REG_CISS_TS_COOL:/*Cool*/ |
| health = POWER_SUPPLY_HEALTH_COOL; |
| break; |
| case ZX234502_REG_CISS_TS_COLD: /* TS1 Cold */ |
| health = POWER_SUPPLY_HEALTH_COLD; |
| break; |
| default: |
| health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| } |
| } |
| |
| val->intval = health; |
| mutex_unlock(&bdi->bs_reg_lock); |
| return 0; |
| } |
| |
| static int zx234502_battery_get_online(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| u8 batfet_disable; |
| int ret; |
| |
| ret = zx234502_read_mask(bdi, ZX234502_REG_CBIS, |
| ZX234502_REG_CBIS_NOBAT_MASK, |
| ZX234502_REG_CBIS_NOBAT_SHIFT, &batfet_disable); |
| if (ret < 0) |
| return ret; |
| |
| val->intval = !batfet_disable; |
| return 0; |
| } |
| #if 0 |
| static int zx234502_battery_get_capacity(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| int ret; |
| uint volt; |
| int bat_levl = 0; |
| int i =0; |
| //union power_supply_propval * online; |
| struct zx234502_bat_calibration *calibration; |
| |
| //zx234502_charger_get_online(bdi,online); |
| |
| u8 v; |
| ret = zx234502_read_mask(bdi, ZX234502_REG_CBIS,ZX234502_REG_CBIS_DCIN_PG_MASK, |
| ZX234502_REG_CBIS_DCIN_PG_SHIFT, &v); |
| if(v) |
| calibration = bdi->pdata->charging; |
| else |
| calibration = bdi->pdata->discharging; |
| //#ifdef CONFIG_ZX234290_ADC |
| volt = get_battery_voltage(); |
| //#endif |
| if (volt > calibration[0].voltage) { |
| bat_levl = calibration[0].level; |
| } else { |
| for (i = 0; calibration[i+1].voltage >= 0; i++) { |
| if (volt <= calibration[i].voltage && |
| volt >= calibration[i+1].voltage) { |
| /* interval found - interpolate within range */ |
| bat_levl = calibration[i].level - |
| ((calibration[i].voltage - volt) * |
| (calibration[i].level - |
| calibration[i+1].level)) / |
| (calibration[i].voltage - |
| calibration[i+1].voltage); |
| break; |
| } |
| } |
| } |
| |
| val->intval = bat_levl; |
| //return 0; |
| |
| return ret; |
| } |
| #endif |
| |
| static int zx234502_battery_set_online(struct zx234502_dev_info *bdi,const union power_supply_propval *val) |
| { |
| return zx234502_write_mask(bdi, ZX234502_REG_MS, |
| ZX234502_REG_MS_ENSHIP_MASK, |
| ZX234502_REG_MS_ENSHIP_SHIFT, !val->intval); /*1:enable 0:disable*/ |
| } |
| |
| |
| static int zx234502_battery_get_property(struct power_supply *psy,enum power_supply_property psp, union power_supply_propval *val) |
| { |
| struct zx234502_dev_info *bdi = |
| container_of(psy, struct zx234502_dev_info, battery); |
| int ret; |
| |
| //dev_dbg(bdi->dev, "prop: %d\n", psp); |
| |
| //pm_runtime_get_sync(bdi->dev); |
| |
| switch (psp) { |
| |
| case POWER_SUPPLY_PROP_HEALTH: |
| ret = zx234502_battery_get_health(bdi, val); |
| break; |
| case POWER_SUPPLY_PROP_ONLINE: |
| ret = zx234502_battery_get_online(bdi, val); |
| break; |
| |
| case POWER_SUPPLY_PROP_TEMP: |
| val->intval = get_adc2_voltage(); |
| ret = 0; |
| break; |
| |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| //#ifdef CONFIG_ZX234290_ADC |
| val->intval = get_adc1_voltage() - 15; |
| ret = 0; |
| break; |
| #if 0 |
| case POWER_SUPPLY_PROP_CAPACITY: |
| ret = zx234502_battery_get_capacity(bdi, val); |
| break; |
| #endif |
| default: |
| ret = -ENODATA; |
| } |
| |
| //pm_runtime_put_sync(bdi->dev); |
| return ret; |
| } |
| |
| static int zx234502_battery_set_property(struct power_supply *psy,enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct zx234502_dev_info *bdi = |
| container_of(psy, struct zx234502_dev_info, battery); |
| int ret; |
| |
| //dev_dbg(bdi->dev, "prop: %d\n", psp); |
| |
| //pm_runtime_put_sync(bdi->dev); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| ret = zx234502_battery_set_online(bdi, val); |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| |
| //pm_runtime_put_sync(bdi->dev); |
| return ret; |
| } |
| |
| static int zx234502_battery_property_is_writeable(struct power_supply *psy,enum power_supply_property psp) |
| { |
| int ret; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| ret = 1; |
| break; |
| default: |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| static enum power_supply_property zx234502_battery_properties[] = { |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| //POWER_SUPPLY_PROP_CAPACITY, |
| }; |
| |
| static void zx234502_battery_init(struct power_supply *battery) |
| { |
| battery->name = "battery"; |
| battery->type = POWER_SUPPLY_PCAC_UNKNOWN; |
| battery->properties = zx234502_battery_properties; |
| battery->num_properties = ARRAY_SIZE(zx234502_battery_properties); |
| battery->get_property = zx234502_battery_get_property; |
| battery->set_property = zx234502_battery_set_property; |
| battery->property_is_writeable = zx234502_battery_property_is_writeable; |
| } |
| |
| |
| static int zx234502_boost_get_online(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| |
| val->intval = bdi->boost_online_flag; |
| return 0; |
| } |
| |
| |
| static int zx234502_boost_get_current_now(struct zx234502_dev_info *bdi,union power_supply_propval *val) |
| { |
| //gpio_set1 is GPIO39 |
| //gpio_set2 is GPIO40 |
| //gpio_set3 is GPIO41 |
| int gpio_set1,gpio_set2,gpio_set3; |
| |
| gpio_set1 = gpio_get_value(bdi->pdata->boost_cur_gpio1); |
| gpio_set2 = gpio_get_value(bdi->pdata->boost_cur_gpio2); |
| gpio_set3 = gpio_get_value(bdi->pdata->boost_cur_gpio3); |
| |
| if((GPIO_LOW==gpio_set1)&(GPIO_LOW==gpio_set2)&(GPIO_HIGH==gpio_set3)){ |
| val->intval = 600; |
| } |
| else if((GPIO_LOW==gpio_set1)&(GPIO_HIGH==gpio_set2)&(GPIO_LOW==gpio_set3)){ |
| val->intval = 1000; |
| } |
| else if((GPIO_LOW==gpio_set1)&(GPIO_HIGH==gpio_set2)&(GPIO_HIGH==gpio_set3)){ |
| val->intval = 1750; |
| } |
| else if((GPIO_HIGH==gpio_set1)&(GPIO_LOW==gpio_set2)&(GPIO_LOW==gpio_set3)){ |
| val->intval = 2000; |
| } |
| else if((GPIO_HIGH==gpio_set1)&(GPIO_LOW==gpio_set2)&(GPIO_HIGH==gpio_set3)){ |
| val->intval = 2850; |
| } |
| else{ |
| |
| dev_dbg(bdi->dev, "The boost current set with err\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| #if 0 |
| static int zx234502_boost_get_current_max(struct zx234502_dev_info *bdi, union power_supply_propval *val) |
| { |
| val->intval = 2850; |
| return 0; |
| } |
| #endif |
| static int zx234502_boost_set_charge_enable(struct zx234502_dev_info *bdi,const union power_supply_propval *val) |
| { |
| zx234502_write_mask(bdi,ZX234502_REG_MS, |
| ZX234502_REG_MS_PWRBNK_MASK,ZX234502_REG_MS_PWRBNK_SHIFT,val->intval); |
| |
| /*set the gpio34 open = HIGH*/ |
| gpio_set_value(bdi->pdata->boost_loadswitch_gpio,val->intval); |
| //zx29_gpio_output_data(bdi->pdata->boost_loadswitch_gpio,GPIO_HIGH); |
| |
| return 0; |
| } |
| |
| static int zx234502_boost_set_current_now(struct zx234502_dev_info *bdi,const union power_supply_propval *val) |
| { |
| //gpio from 35/36/37 changed to 39/40/41 |
| //gpio_set1 is GPIO39 |
| //gpio_set2 is GPIO40 |
| //gpio_set3 is GPIO41 |
| int gpio_set1,gpio_set2,gpio_set3; |
| |
| if(val->intval < 601){ |
| /*0.6A GPIO35:LOW GPIO36:LOW GPIO37:HIGH 40k */ |
| gpio_set1 = GPIO_LOW; |
| gpio_set2 = GPIO_LOW; |
| gpio_set3 = GPIO_HIGH; |
| } |
| else if( (600 < val->intval)&&(val->intval< 1001)){ |
| /*1A GPIO35:LOW GPIO36:HIGH GPIO37:LOW 20k */ |
| gpio_set1 = GPIO_LOW; |
| gpio_set2 = GPIO_HIGH; |
| gpio_set3 = GPIO_LOW; |
| } |
| else if( (1000 < val->intval)&&(val->intval < 1751)){ |
| /*1.75A GPIO35:LOW GPIO36:HIGH GPIO37:HIGH 13.3k*/ |
| gpio_set1 = GPIO_LOW; |
| gpio_set2 = GPIO_HIGH; |
| gpio_set3 = GPIO_HIGH; |
| } |
| else if(( 1750 < val->intval) &&( val->intval < 2001)){ |
| /*2A GPIO35:HIGH GPIO36:LOW GPIO37:LOW 10k */ |
| gpio_set1 = GPIO_HIGH; |
| gpio_set2 = GPIO_LOW; |
| gpio_set3 = GPIO_LOW; |
| } |
| else{ |
| /*2.85A GPIO35:HIGH GPIO36:LOW GPIO37:HIGH 8k*/ |
| gpio_set1 = GPIO_HIGH; |
| gpio_set2 = GPIO_LOW; |
| gpio_set3 = GPIO_HIGH; |
| } |
| |
| gpio_set_value(bdi->pdata->boost_cur_gpio1, gpio_set1); |
| gpio_set_value(bdi->pdata->boost_cur_gpio2, gpio_set2); |
| gpio_set_value(bdi->pdata->boost_cur_gpio3, gpio_set3); |
| return 0; |
| } |
| |
| static int zx234502_boost_get_property(struct power_supply *psy,enum power_supply_property psp, union power_supply_propval *val) |
| { |
| struct zx234502_dev_info *bdi = |
| container_of(psy, struct zx234502_dev_info, boost); |
| int ret; |
| |
| //dev_dbg(bdi->dev, "prop: %d\n", psp); |
| |
| //pm_runtime_get_sync(bdi->dev); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_ONLINE: |
| ret = zx234502_boost_get_online(bdi, val); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| ret = zx234502_boost_get_current_now(bdi, val); |
| break; |
| #if 0 |
| case POWER_SUPPLY_PROP_CURRENT_MAX: |
| ret = zx234502_boost_get_current_max(bdi, val); |
| break; |
| #endif |
| default: |
| ret = -ENODATA; |
| } |
| |
| //pm_runtime_put_sync(bdi->dev); |
| return ret; |
| } |
| |
| static int zx234502_boost_set_property(struct power_supply *psy,enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct zx234502_dev_info *bdi = |
| container_of(psy, struct zx234502_dev_info, boost); |
| int ret; |
| |
| //dev_dbg(bdi->dev, "prop: %d\n", psp); |
| |
| //pm_runtime_put_sync(bdi->dev); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_BOOST_ENABLE: |
| ret = zx234502_boost_set_charge_enable(bdi, val); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| ret = zx234502_boost_set_current_now(bdi, val); |
| break; |
| |
| default: |
| ret = -EINVAL; |
| } |
| |
| //pm_runtime_put_sync(bdi->dev); |
| return ret; |
| } |
| |
| static int zx234502_boost_property_is_writeable(struct power_supply *psy,enum power_supply_property psp) |
| { |
| int ret; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_BOOST_ENABLE: |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| ret = 1; |
| break; |
| default: |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| static enum power_supply_property zx234502_boost_properties[] = { |
| POWER_SUPPLY_PROP_ONLINE, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| //WER_SUPPLY_PROP_CURRENT_MAX, |
| POWER_SUPPLY_PROP_BOOST_ENABLE, |
| }; |
| |
| static void zx234502_boost_init(struct power_supply *boost) |
| { |
| #ifdef DBG_CHARGE |
| //printk(KERN_INFO "zx234502_boost_init =%x.\n",boost); |
| #endif |
| boost->name = "boost"; |
| boost->type = POWER_SUPPLY_PCAC_UNKNOWN; |
| boost->properties = zx234502_boost_properties; |
| boost->num_properties = ARRAY_SIZE(zx234502_boost_properties); |
| boost->get_property = zx234502_boost_get_property; |
| boost->set_property = zx234502_boost_set_property; |
| boost->property_is_writeable = zx234502_boost_property_is_writeable; |
| } |
| |
| |
| static int zx234502_charger_gpio_config(struct device *dev,int pin,gpio_func_id func_sel,char *name) |
| { |
| int ret = -1; |
| |
| if (gpio_is_valid(pin)){ |
| ret = gpio_request(pin, name); |
| if (ret){ |
| dev_err(dev, "cannot get [%s] gpio\n",name); |
| return -1; |
| } |
| ret = zx29_gpio_config(pin,func_sel); |
| if(ret){ |
| dev_err(dev, "cannot config [%s] gpio\n",name); |
| gpio_free(pin); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #if 0 |
| //zx234500_charger_gpio_config_input |
| static void zx234502_charger_gpio_config_input(struct device *dev,int gpio,gpio_func_id func_sel,char *str) |
| { |
| if(zx234502_charger_gpio_config(dev,gpio,func_sel,str)!=0) |
| return; |
| zx29_gpio_set_direction(gpio,GPIO_IN); |
| zx29_gpio_input_data(gpio); |
| } |
| #endif |
| |
| //zx234500_charger_gpio_config_output |
| static int zx234502_charger_gpio_config_output(struct device *dev,int gpio,gpio_func_id func_sel,char *str,int value2) |
| { |
| if(zx234502_charger_gpio_config(dev,gpio,func_sel,str)!=0) |
| return -1; |
| zx29_gpio_set_direction(gpio,GPIO_OUT); |
| zx29_gpio_output_data(gpio,value2); |
| |
| return 0; |
| } |
| |
| static int zx234502_boost_gpio_init(struct device *dev, struct zx234502_platform_data *pdata) |
| { |
| int ret = -1; |
| |
| ret = zx234502_charger_gpio_config_output(dev,pdata->boost_loadswitch_gpio,pdata->boost_loadswitch_fun_sel,"LoadSwitch",GPIO_LOW); |
| /*set the out put current 1A*/ |
| ret += zx234502_charger_gpio_config_output(dev,pdata->boost_cur_gpio1,pdata->boost_gpio1_fun_sel,"Iset1",GPIO_LOW); |
| ret += zx234502_charger_gpio_config_output(dev,pdata->boost_cur_gpio2,pdata->boost_gpio2_fun_sel,"Iset2",GPIO_HIGH); |
| ret += zx234502_charger_gpio_config_output(dev,pdata->boost_cur_gpio3,pdata->boost_gpio3_fun_sel,"Iset3",GPIO_LOW); |
| |
| return ret; |
| } |
| |
| static void zx234502_boost_gpio_uninit(struct zx234502_platform_data *pdata) |
| { |
| gpio_free(pdata->boost_loadswitch_gpio); |
| gpio_free(pdata->boost_cur_gpio1); |
| gpio_free(pdata->boost_cur_gpio2); |
| gpio_free(pdata->boost_cur_gpio3); |
| } |
| |
| /* |
| when BAT goes under BAEdet(3.4V) or CHGRST set 1 |
| Group B(A1,A2,A3,A4,A5,A7) registers will reset |
| call this function in init or dcin |
| */ |
| static int zx234502_charger_config_groupB_Regs(struct zx234502_dev_info *bdi) |
| { |
| int ret; |
| |
| /*set VINDPM to 4.52V*/ |
| ret = zx234502_write_mask(bdi,ZX234502_REG_ISC, |
| ZX234502_REG_ISC_VINDPM_MASK, |
| ZX234502_REG_ISC_VINDPM_SHIFT, |
| 0x8); |
| /*set VSETA to 4.2V*/ |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CVC, |
| ZX234502_REG_CVC_VBAT_MASK, |
| ZX234502_REG_CVC_VBAT_SHIFT, |
| 0x2); |
| |
| /*disable the timer*/ |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CTC, |
| ZX234502_REG_CTC_EN_TIMER_MASK, |
| ZX234502_REG_CTC_EN_TIMER_SHIFT, |
| 0x0); |
| /*disable the 2*timer*/ |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CTC, |
| ZX234502_REG_CTC_EN_2XTIMER_MASK, |
| ZX234502_REG_CTC_EN_2XTIMER_SHIFT, |
| 0x0); |
| /*set CVCOMP to 20mV*/ |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CTC, |
| ZX234502_REG_CTC_CVCOMP_MASK, |
| ZX234502_REG_CTC_CVCOMP_SHIFT, |
| 0x4); |
| |
| |
| /*disable the JETTA*/ |
| ret += zx234502_write_mask(bdi,ZX234502_REG_THR, |
| ZX234502_REG_THR_EN_JEITA_MASK, |
| ZX234502_REG_THR_EN_JEITA_SHIFT, |
| 0x0); |
| |
| /*set the temprator cool */ |
| /*00:0 01:5 10:10 11:15*/ |
| ret += zx234502_write_mask(bdi,ZX234502_REG_TS, |
| ZX234502_REG_TS_COOL_MASK, |
| ZX234502_REG_TS_COOL_SHIFT, |
| 0x0); |
| |
| if (ret < 0){ |
| goto out; |
| } |
| |
| return 0; |
| |
| out: |
| //pm_runtime_put_sync(bdi->dev); |
| dev_err(bdi->dev, "zx234502_charger_groupB_config ERROR: %d\n", ret); |
| return ret; |
| |
| } |
| |
| static int zx234502_hw_init(struct zx234502_dev_info *bdi) |
| { |
| u8 v; |
| int ret; |
| |
| union power_supply_propval voltage_val = {0}; |
| |
| #ifdef DBG_CHARGE |
| //set pshold1 1,just for test |
| //gpio_request(ZX29_GPIO_51, "pshold1"); |
| //zx29_gpio_config(ZX29_GPIO_51, GPIO51_GPIO51); |
| //zx29_gpio_set_direction(ZX29_GPIO_51, GPIO_OUT); |
| //zx29_gpio_output_data(ZX29_GPIO_51, 1); |
| #endif |
| //pm_runtime_get_sync(bdi->dev); |
| |
| /* First check that the device really is what its supposed to be */ |
| ret = zx234502_read_mask(bdi, ZX234502_REG_VER, |
| ZX234502_REG_VER_INFO_MASK, |
| ZX234502_REG_VER_INFO_SHIFT, |
| &v); |
| |
| if (ret < 0){ |
| goto out; |
| } |
| //printk(KERN_INFO "zx234502_hw_init:zx234502-charger version reg: 0x%x\n", v); |
| /* |
| if (v != bdi->model) { |
| ret = -ENODEV; |
| goto out; |
| } |
| */ |
| voltage_val.intval = 4350; |
| ret = zx234502_charger_set_voltage(bdi,&voltage_val); |
| if (ret < 0){ |
| goto out; |
| } |
| |
| /*disable the timer*/ |
| ret = zx234502_write_mask(bdi,ZX234502_REG_CTC, |
| ZX234502_REG_CTC_EN_TIMER_MASK, |
| ZX234502_REG_CTC_EN_TIMER_SHIFT, |
| 0x0); |
| if (ret < 0){ |
| goto out; |
| } |
| /*disable the 2*timer*/ |
| ret = zx234502_write_mask(bdi,ZX234502_REG_CTC, |
| ZX234502_REG_CTC_EN_2XTIMER_MASK, |
| ZX234502_REG_CTC_EN_2XTIMER_SHIFT, |
| 0x0); |
| |
| if (ret < 0){ |
| goto out; |
| } |
| |
| //enable PMUON |
| //ret = zx234502_write_mask(bdi,ZX234502_REG_MS, |
| // ZX234502_REG_MS_PMUON_MASK, |
| // ZX234502_REG_MS_PMUON_SHIFT, |
| // 0x1); |
| //config NTCDET |
| // 0:enable auto ntc detection |
| // 1:disable auto ntc detection |
| ret += zx234502_write_mask(bdi,ZX234502_REG_PTS, |
| ZX234502_REG_PTS_NTCDET_MASK, |
| ZX234502_REG_PTS_NTCDET_SHIFT, |
| 0x1); |
| //when NTCDET is 1 ,TSSEL is to select R |
| // 0:10k |
| // 1:100k |
| //ret += zx234502_write_mask(bdi,ZX234502_REG_PTS, |
| // ZX234502_REG_PTS_TSSEL_MASK, |
| // ZX234502_REG_PTS_TSSEL_SHIFT, |
| // 0x0); |
| |
| /*disable the charger detect*/ |
| ret += zx234502_write_mask(bdi,ZX234502_REG_THR, |
| ZX234502_REG_THR_EN_2DET_MASK, |
| ZX234502_REG_THR_EN_2DET_SHIFT, |
| 0x0); |
| ret += zx234502_write_mask(bdi,ZX234502_REG_THR, |
| ZX234502_REG_THR_EN_1DET_MASK, |
| ZX234502_REG_THR_EN_1DET_SHIFT, |
| 0x0); |
| //enable charger |
| ret += zx234502_write_mask(bdi,ZX234502_REG_MS, |
| ZX234502_REG_MS_ENCHG_MASK, |
| ZX234502_REG_MS_ENCHG_SHIFT, |
| 0x0); |
| ret += zx234502_write_mask(bdi,ZX234502_REG_MS, |
| ZX234502_REG_MS_INSUS_MASK, |
| ZX234502_REG_MS_INSUS_SHIFT, |
| 0x0); |
| //mask /SAFE it |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CBIM,ZX234502_REG_CBIM_MASK_SAFE_TIMER_MASK, |
| ZX234502_REG_CBIM_MASK_SAFE_TIMER_SHIFT,0x1); |
| //mask CHGRUN it |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CIIM,ZX234502_REG_CIIM_MASK_CHGRUN_MASK, |
| ZX234502_REG_CIIM_MASK_CHGRUN_SHIFT,0x1); |
| //mask INLIMIT it |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CIIM,ZX234502_REG_CIIM_MASK_INLIMIT_MASK, |
| ZX234502_REG_CIIM_MASK_INLIMIT_SHIFT,0x1); |
| //mask DCDET it |
| ret += zx234502_write_mask(bdi,ZX234502_REG_CIIM,ZX234502_REG_CIIM_MASK_DCDET_MASK, |
| ZX234502_REG_CIIM_MASK_DCDET_SHIFT,0x1); |
| if (ret < 0){ |
| goto out; |
| } |
| |
| //set ts_meter interrupt mask |
| if(true !=bdi->pdata->ts_flag){ |
| zx234502_write_mask(bdi,ZX234502_REG_CIIM,ZX234502_REG_CIIM_MASK_TS_MASK, |
| ZX234502_REG_CIIM_MASK_TS_SHIFT,0x1); |
| } |
| |
| return 0; |
| |
| out: |
| //pm_runtime_put_sync(bdi->dev); |
| dev_err(bdi->dev, "zx234502_hw_init err: %d\n", ret); |
| return ret; |
| } |
| |
| |
| static irqreturn_t zx234502_charger_irq_primary_handler(int irq, struct zx234502_dev_info *bdi) |
| { |
| disable_irq_nosync(irq); |
| //pcu_int_clear(irq); |
| pcu_clr_irq_pending(irq); |
| |
| up(&bdi->chgirq_sem); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t zx234502_irq_handler_thread(void *data) |
| { |
| struct zx234502_dev_info *bdi = data; |
| bool charger_changed_flag = false; |
| bool battery_changed_flag = false; |
| bool boost_changed_flag = false; |
| int ret; |
| u8 ms; |
| struct sched_param param = { .sched_priority = 2 }; |
| param.sched_priority= 31; |
| sched_setscheduler(current, SCHED_FIFO, ¶m); |
| |
| while(1) |
| { |
| down(&bdi->chgirq_sem); |
| //pm_runtime_get_sync(bdi->dev); |
| mutex_lock(&bdi->bs_reg_lock); |
| bdi->cbis_pre_reg=bdi->cbis_curr_reg; |
| bdi->pis_pre_reg=bdi->pis_curr_reg; |
| bdi->ciis_pre_reg=bdi->ciis_curr_reg; |
| bdi->cfis_pre_reg = bdi->cfis_curr_reg; |
| |
| //printk(KERN_INFO"zx234502_irq_handler_thread\n"); |
| |
| |
| ret = zx234502_read(bdi, ZX234502_REG_MS, &ms); |
| if (ret < 0) { |
| dev_err(bdi->dev, "Can't read MS reg: %d\n", ret); |
| mutex_unlock(&bdi->bs_reg_lock); |
| goto out; |
| } |
| //printk("zx234502 MS reg is %d",ms); |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CBIS, &bdi->cbis_curr_reg); |
| if (ret < 0) { |
| dev_err(bdi->dev, "Can't read CBIS reg: %d\n", ret); |
| mutex_unlock(&bdi->bs_reg_lock); |
| goto out; |
| } |
| |
| |
| ret = zx234502_read(bdi, ZX234502_REG_PIS, &bdi->pis_curr_reg); |
| if (ret < 0) { |
| dev_err(bdi->dev, "Can't read PIS reg: %d\n", ret); |
| mutex_unlock(&bdi->bs_reg_lock); |
| goto out; |
| } |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CIIS, &bdi->ciis_curr_reg); |
| if (ret < 0) { |
| dev_err(bdi->dev, "Can't read CIIS reg: %d\n", ret); |
| mutex_unlock(&bdi->bs_reg_lock); |
| goto out; |
| } |
| printk("cbis_reg: 0x%02x, pis_reg: 0x%02x, ciis_reg: 0x%02x\n", |
| bdi->cbis_curr_reg, bdi->pis_curr_reg, bdi->ciis_curr_reg); |
| /*clear the A10 int*/ |
| ret = zx234502_write_mask(bdi, ZX234502_REG_CIIS,ZX234502_REG_CIIS_INT_MASK,ZX234502_REG_CIIS_INT_SHIFT,0); |
| |
| if (ret < 0) { |
| printk(KERN_INFO"chg clear int failed 1\n"); |
| ret = zx234502_write_mask(bdi, ZX234502_REG_CIIS,ZX234502_REG_CIIS_INT_MASK,ZX234502_REG_CIIS_INT_SHIFT,0); |
| if (ret < 0) { |
| printk(KERN_INFO"chg clear int failed 2\n"); |
| ret = zx234502_write_mask(bdi, ZX234502_REG_CIIS,ZX234502_REG_CIIS_INT_MASK,ZX234502_REG_CIIS_INT_SHIFT,0); |
| if (ret < 0) { |
| printk(KERN_INFO"chg clear int failed 3\n"); |
| } |
| else{ |
| printk(KERN_INFO"chg clear int failed ok 3\n"); |
| //panic(0); |
| } |
| } |
| else{ |
| printk(KERN_INFO"chg clear int failed ok 2\n"); |
| } |
| } |
| |
| if(bdi->cbis_curr_reg != bdi->cbis_pre_reg){ |
| if ((bdi->cbis_curr_reg & ZX234502_REG_CBIS_DCIN_PG_MASK) != (bdi->cbis_pre_reg & ZX234502_REG_CBIS_DCIN_PG_MASK)) { |
| if(bdi->cbis_curr_reg & ZX234502_REG_CBIS_DCIN_PG_MASK){ |
| //zx234502_hw_init(bdi); |
| printk(KERN_INFO"chg usb in\n"); |
| dwc_otg_chg_inform(0);/*usb in*/ |
| |
| ret = zx234502_charger_config_groupB_Regs(bdi); |
| if(ret) |
| { |
| printk(KERN_INFO"chg config groupB Regs failed\n"); |
| } |
| // ret = zx234502_write_mask(bdi,ZX234502_REG_MS,ZX234502_REG_MS_PMUON_MASK,ZX234502_REG_MS_PMUON_SHIFT,0x1); |
| } |
| else{ |
| printk(KERN_INFO"chg usb out\n"); |
| dwc_otg_chg_inform(1);/*usb out*/ |
| bdi->charger.type = POWER_SUPPLY_PCAC_UNKNOWN; |
| // ret = zx234502_write_mask(bdi,ZX234502_REG_MS,ZX234502_REG_MS_PMUON_MASK,ZX234502_REG_MS_PMUON_SHIFT,0x0); |
| } |
| } |
| charger_changed_flag = true; |
| } |
| |
| if(true == bdi->pdata->boost_flag){ |
| if (bdi->pis_curr_reg != bdi->pis_pre_reg) { |
| if(bdi->pis_curr_reg & ZX234502_REG_PIS_POWERON_IT_MASK){ |
| queue_delayed_work(g_bdi->boostQueue,&bdi->boostWorkStruct,2000); |
| } |
| boost_changed_flag = true; |
| bdi->boost_online_flag = 1; |
| } |
| } |
| |
| if (bdi->ciis_curr_reg != bdi->ciis_pre_reg) { |
| if(true == bdi->pdata->ts_flag){ |
| if((bdi->ciis_curr_reg & ZX234502_REG_CIIS_TS_METER_MASK) != (bdi->ciis_pre_reg & ZX234502_REG_CIIS_TS_METER_MASK)){ |
| #ifdef DBG_CHARGE |
| switch((bdi->ciis_curr_reg&ZX234502_REG_CIIS_TS_METER_MASK )>>ZX234502_REG_CIIS_TS_METER_SHIFT){ |
| case 0: |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO"chg bat temp nomrmal\n"); |
| #endif |
| break; |
| case ZX234502_REG_CISS_TS_WARM:/*warm*/ |
| /*in warm state,stop boost*/ |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO"chg bat temp warm \n"); |
| #endif |
| break; |
| case ZX234502_REG_CISS_TS_COOL:/*cool*/ |
| /*in cool state,stop boost*/ |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO"chg bat temp cool\n"); |
| #endif |
| //chg_disable.intval = 1; |
| //pwrbnk_enable.intval = 0; |
| //zx234502_charger_set_charger_config(bdi,&chg_disable); |
| //zx234502_boost_set_charge_enable(bdi, &pwrbnk_enable); |
| break; |
| case 3:/*hot*/ |
| /*in hot state,charger chip auto off*/ |
| printk(KERN_INFO"chg bat temp hot \n"); |
| break; |
| case 6:/*cold*/ |
| /*in cold state,charger chip auto off*/ |
| printk(KERN_INFO"chg bat temp cold\n"); |
| break; |
| default: |
| printk(KERN_INFO"chg bat temp unkonw\n"); |
| break; |
| } |
| #endif |
| } |
| } |
| battery_changed_flag = true; |
| } |
| bdi->charger_health_valid = true;/****?***/ |
| bdi->battery_health_valid = true;/****?***/ |
| bdi->battery_status_valid = true;/****?***/ |
| |
| mutex_unlock(&bdi->bs_reg_lock); |
| |
| /* |
| * Sometimes zx234502 gives a steady trickle of interrupts even |
| * though the watchdog timer is turned off and neither the STATUS |
| * nor FAULT registers have changed. Weed out these sprurious |
| * interrupts so userspace isn't alerted for no reason. |
| * In addition, the chip always generates an interrupt after |
| * register reset so we should ignore that one (the very first |
| * interrupt received). |
| */ |
| //if (charger_changed_flag && !bdi->first_time) |
| if (charger_changed_flag ) |
| power_supply_changed(&bdi->charger); |
| //if (battery_changed_flag && !bdi->first_time) |
| if (battery_changed_flag) |
| power_supply_changed(&bdi->battery); |
| if (true == bdi->pdata->boost_flag){ |
| //if (boost_changed_flag && !bdi->first_time) |
| if (boost_changed_flag ) |
| power_supply_changed(&bdi->boost); |
| } |
| |
| bdi->first_time = false; |
| |
| out: |
| //pm_runtime_put_sync(bdi->dev); |
| |
| dev_dbg(bdi->dev, "read reg:cbis_reg: 0x%02x, pis_reg: 0x%02x, ciis_reg: 0x%02x\n", |
| bdi->cbis_curr_reg, bdi->pis_curr_reg, bdi->ciis_curr_reg); |
| |
| enable_irq(bdi->irq); |
| } |
| return 0; |
| } |
| |
| |
| static int zx234502_setup_pdata(struct zx234502_dev_info *bdi, |
| struct zx234502_platform_data *pdata) |
| { |
| int ret; |
| |
| #if 0 |
| if (!gpio_is_valid(pdata->gpio_int)) |
| return -1; |
| |
| ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev)); |
| if (ret < 0) |
| return -1; |
| |
| ret = gpio_direction_input(pdata->gpio_int); |
| if (ret < 0) |
| goto out; |
| |
| bdi->irq = gpio_to_irq(pdata->gpio_int); |
| if (!bdi->irq) |
| goto out; |
| #endif |
| |
| bdi->irq = gpio_to_irq(pdata->gpio_int); |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO"zx234502_setup_pdata irq= %d\n",bdi->irq); |
| #endif |
| /*Ñ¡Ôñ´Ë¸´Óù¦ÄÜΪÖжϹ¦ÄÜ*/ |
| ret = zx29_gpio_config(pdata->gpio_int,pdata->gpio_int_fun_sel);/********GPIO11:0 /EXT_INT:5*******/ |
| if (ret < 0){ |
| printk(KERN_INFO"zx234502 zx29_gpio_config error int= %d,fun= %d\n",pdata->gpio_int,pdata->gpio_int_fun_sel); |
| return -1; |
| } |
| |
| zx29_gpio_set_inttype(pdata->gpio_int,IRQ_TYPE_EDGE_FALLING/*IRQ_TYPE_EDGE_RISING*/); //INT_POSEDGE |
| zx29_gpio_pd_pu_set(pdata->gpio_int, IO_CFG_PULL_DISABLE); |
| |
| bdi->gpio_int = pdata->gpio_int; |
| //pcu_int_clear(bdi->irq); |
| pcu_clr_irq_pending(bdi->irq); |
| return 0; |
| #if 0 |
| out: |
| gpio_free(pdata->gpio_int); |
| return -1; |
| #endif |
| |
| } |
| |
| |
| static int zx234502_init_state(struct zx234502_dev_info *bdi) |
| { |
| u8 cfis_reg,pis_reg,cbis_reg,ciis_reg; |
| int ret; |
| |
| printk(KERN_INFO "zx234502_init_state start.\n"); |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CBIS, &cbis_reg); |
| if (ret < 0){ |
| printk(KERN_INFO "charger:init state:Can't read CBIS reg: 0x%x\n", cbis_reg); |
| return ret; |
| } |
| |
| ret = zx234502_read(bdi, ZX234502_REG_PIS, &pis_reg); |
| if (ret < 0) { |
| printk(KERN_INFO "charger:init Power On /PMID state:Can't read PIS reg: %d\n", ret); |
| return ret; |
| } |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CFIS, &cfis_reg); |
| if (ret < 0) { |
| printk(KERN_INFO "charger:init Power On /PMID state:Can't read CFIS reg: %d\n", ret); |
| return ret; |
| } |
| |
| ret = zx234502_read(bdi, ZX234502_REG_CIIS, &ciis_reg); |
| if (ret < 0) { |
| printk(KERN_INFO "charger:init Power On /PMID state:Can't read CISS reg: %d\n", ret); |
| return ret; |
| } |
| |
| mutex_lock(&bdi->bs_reg_lock); |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO "zx234502_init_state get lock ok.\n"); |
| #endif |
| bdi->cbis_curr_reg= cbis_reg; |
| bdi->pis_curr_reg= pis_reg; |
| bdi->cfis_curr_reg= cfis_reg; |
| bdi->ciis_curr_reg = ciis_reg; |
| bdi->charger.type = POWER_SUPPLY_TYPE_UNKNOWN; |
| |
| if(bdi->cbis_curr_reg & ZX234502_REG_CBIS_DCIN_PG_MASK){ |
| printk(KERN_INFO"send ap usb in message\n"); |
| dwc_otg_chg_inform(0);/*usb in*/ |
| } |
| else{ |
| printk(KERN_INFO"send ap usb out message\n"); |
| dwc_otg_chg_inform(1);/*usb out*/ |
| } |
| #ifdef DBG_CHARGE |
| if(bdi->cbis_curr_reg & BIT(5)) |
| printk(KERN_INFO "no batterty.\n"); |
| else |
| printk(KERN_INFO " baterry installed.\n"); |
| #endif |
| mutex_unlock(&bdi->bs_reg_lock); |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO "zx234502_init_state:cfis_reg10=0x%x,pis_reg0E=0x%x,cbis_reg0C=0x%x,ciis_reg0A=0x%x.\n",cfis_reg,pis_reg,cbis_reg,ciis_reg); |
| #endif |
| |
| return 0; |
| } |
| |
| |
| static void zx234502_boost_workstruct_callback(struct work_struct *work ) |
| { |
| uint vol_curr = 0; |
| #ifdef DBG_CHARGE |
| u8 mainset; |
| #endif |
| struct zx234502_dev_info *bdi = |
| container_of(work, struct zx234502_dev_info, boostWorkStruct); |
| vol_curr = get_adc2_voltage();/*get boost current*/ |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO"boost current is %d in\n",vol_curr); |
| zx234502_read(bdi, 0, &mainset); |
| printk(KERN_INFO"powerbank is %d in\n",mainset); |
| printk(KERN_INFO"workqueue test\n"); |
| #endif |
| if (vol_curr < ZX234502_BOOST_V_LIMIT)/*50mV*/{ |
| bdi->boostcount++; |
| } |
| if(bdi->boostcount > 3){ |
| bdi->boostcount = 0; |
| bdi->boost_online_flag = 0; |
| power_supply_changed(&bdi->boost); |
| cancel_delayed_work_sync(&bdi->boostWorkStruct); |
| } |
| else{ |
| bdi->boost_online_flag = 1; |
| queue_delayed_work(bdi->boostQueue,&bdi->boostWorkStruct,2000); |
| } |
| |
| } |
| static void zx234502_charge_typedet(T_TYPE_USB_DETECT chg_type) |
| { |
| int ret; |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO"charge type is %d in\n",chg_type); |
| #endif |
| if(TYPE_ADAPTER == chg_type){ |
| |
| printk(KERN_INFO"chg type is TYPE_ADAPTER\n"); |
| /*set the DCIN Current = 1A*/ |
| ret = zx234502_write_mask(g_bdi,ZX234502_REG_ISC, |
| ZX234502_REG_ISC_ISET_DCIN_MASK, |
| ZX234502_REG_ISC_ISET_DCIN_SHIFT, |
| 0x3); |
| if (ret < 0){ |
| printk(KERN_INFO"write REG_01 fault\n"); |
| } |
| g_bdi->charger.type = POWER_SUPPLY_PCAC__AC; |
| } |
| |
| else{ |
| printk(KERN_INFO"chgage type is TYPE_PC\n"); |
| |
| /*set the DCIN Current = 450mA*/ |
| ret = zx234502_write_mask(g_bdi,ZX234502_REG_ISC, |
| ZX234502_REG_ISC_ISET_DCIN_MASK, |
| ZX234502_REG_ISC_ISET_DCIN_SHIFT, |
| 0x1); |
| if (ret < 0){ |
| printk(KERN_INFO"write REG_01 fault\n"); |
| } |
| |
| g_bdi->charger.type = POWER_SUPPLY_PCAC__PC; |
| } |
| |
| |
| #ifdef CONFIG_CHARGER_ZX234502_EVB |
| /*set the DCIN Current = 2A*/ |
| ret = zx234502_write_mask(g_bdi,ZX234502_REG_ISC, |
| ZX234502_REG_ISC_ISET_DCIN_MASK, |
| ZX234502_REG_ISC_ISET_DCIN_SHIFT, |
| 0x5); |
| if (ret < 0){ |
| printk(KERN_INFO"write REG_01 fault\n"); |
| } |
| #endif |
| |
| } |
| |
| |
| #if defined(CONFIG_DEBUG_FS) |
| static ssize_t debugfs_regs_write(struct file *file, const char __user *buf,size_t nbytes, loff_t *ppos) |
| { |
| unsigned int val1, val2; |
| u8 reg, value; |
| int ret; |
| char *kern_buf; |
| struct seq_file *s = file->private_data; |
| struct zx234502_dev_info *zx234502 = s->private; |
| |
| kern_buf = kzalloc(nbytes, GFP_KERNEL); |
| |
| if (!kern_buf) { |
| printk(KERN_INFO "zx234502_charger: Failed to allocate buffer\n"); |
| return -ENOMEM; |
| } |
| |
| if (copy_from_user(kern_buf, (void __user *)buf, nbytes)) { |
| kfree(kern_buf); |
| return -ENOMEM; |
| } |
| printk(KERN_INFO "%s input str=%s,nbytes=%d \n", __func__, kern_buf,nbytes); |
| |
| ret = sscanf(kern_buf, "%x:%x", &val1, &val2); |
| if (ret < 2 || val1 > ZX234502_REG_MAX ) { |
| printk(KERN_INFO "zx234502_charger: failed to read user buf, ret=%d, input 0x%x:0x%x\n", |
| ret, val1, val2); |
| kfree(kern_buf); |
| return -EINVAL; |
| } |
| kfree(kern_buf); |
| |
| reg = val1 & 0xff; |
| value = val2 & 0xff; |
| printk(KERN_INFO "%s input %x,%x; reg=%x,value=%x\n", __func__, val1, val2, reg, value); |
| ret = zx234502_write(zx234502,reg, value); |
| |
| return ret ? ret : nbytes; |
| } |
| |
| static int debugfs_regs_show(struct seq_file *s, void *v) |
| { |
| int i; |
| u8 value[ZX234502_REG_MAX]; |
| int ret=0; |
| int curr = 0; |
| struct zx234502_dev_info *zx234502 = s->private; |
| |
| for (i = 0; i < ZX234502_REG_MAX; i++){ |
| ret = zx234502_read(zx234502, i, &(value[i])); |
| |
| if(ret){ |
| printk(KERN_INFO "%s err=%d, break\n", __func__, ret); |
| seq_printf(s, "%s err=%d, break", __func__, ret); |
| return ret; |
| } |
| } |
| |
| for (i = 0; i < ZX234502_REG_MAX; i++) { |
| if((i+1)%9 == 0) |
| seq_printf(s, "\n"); |
| |
| seq_printf(s, "[0x%x]%02x ", i, value[i]); |
| } |
| /*charger type*/ |
| if(zx234502->charger.type ==POWER_SUPPLY_PCAC__PC){ |
| seq_printf(s, "charger type is PC\n"); |
| } |
| else if(zx234502->charger.type ==POWER_SUPPLY_PCAC__AC){ |
| seq_printf(s, "charger type is AC\n"); |
| } |
| else |
| seq_printf(s, "charger type is unknow = %d\n",zx234502->charger.type); |
| |
| /*stopchg_flag*/ |
| if(1==stopchg_flag){ |
| seq_printf(s, "mmi set charger enable,the charger state = %d\n",!(value[ZX234502_REG_MS]&ZX234502_REG_MS_INSUS_MASK)); |
| } |
| else if(2==stopchg_flag){ |
| seq_printf(s, "mmi set charger disable,the charger state = %d\n",!(value[ZX234502_REG_MS]&ZX234502_REG_MS_INSUS_MASK)); |
| } |
| else |
| seq_printf(s, "mmi never set the charger ,the charger state = %d\n",!(value[ZX234502_REG_MS]&ZX234502_REG_MS_INSUS_MASK)); |
| |
| /*charge current*/ |
| ret = zx234502_get_field_val(zx234502, ZX234502_REG_ISC, |
| ZX234502_REG_ISC_ISET_DCIN_MASK, ZX234502_REG_ISC_ISET_DCIN_SHIFT, |
| zx234502_in_ilimit_values, |
| ARRAY_SIZE(zx234502_in_ilimit_values), &curr); |
| |
| if (ret < 0) |
| return ret; |
| seq_printf(s, "the charger current now = %dmA\n",curr); |
| |
| |
| return ret; |
| } |
| |
| #define DEBUGFS_FILE_ENTRY(name) \ |
| static int debugfs_##name##_open(struct inode *inode, struct file *file) \ |
| {\ |
| return single_open(file, debugfs_##name##_show, inode->i_private); \ |
| }\ |
| \ |
| static const struct file_operations debugfs_##name##_fops = { \ |
| .owner= THIS_MODULE, \ |
| .open= debugfs_##name##_open, \ |
| .write=debugfs_##name##_write, \ |
| .read= seq_read, \ |
| .llseek= seq_lseek, \ |
| .release= single_release, \ |
| } |
| |
| DEBUGFS_FILE_ENTRY(regs); |
| |
| static struct dentry *g_charger_root; |
| |
| static void debugfs_charger_init(struct zx234502_dev_info *zx234502) |
| { |
| struct dentry *root; |
| struct dentry *node; |
| int i; |
| |
| if(!zx234502) |
| return; |
| |
| //create root |
| root = debugfs_create_dir("charger_zx29", NULL); |
| if (!root) { |
| dev_err(&zx234502->dev, "debugfs_create_dir err=%d\n", IS_ERR(root)); |
| goto err; |
| } |
| |
| //print regs; |
| node = debugfs_create_file("regs", S_IRUGO | S_IWUGO, root, zx234502, &debugfs_regs_fops); |
| if (!node){ |
| dev_err(&zx234502->dev, "debugfs_create_dir err=%d\n", IS_ERR(node)); |
| goto err; |
| } |
| |
| g_charger_root = (void *)root; |
| return; |
| err: |
| dev_err(&zx234502->dev, "debugfs_charger_init err\n"); |
| } |
| |
| #endif |
| |
| |
| static int zx234502_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); |
| struct device *dev = &client->dev; |
| struct zx234502_platform_data *pdata = client->dev.platform_data; |
| struct zx234502_dev_info *bdi; |
| int ret; |
| int i; /*when err try 3 times*/ |
| //use for other |
| //g_platform_data= client->dev.platform_data; |
| |
| printk(KERN_INFO "charger probe.\n"); |
| |
| if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { |
| dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); |
| return -ENODEV; |
| } |
| |
| bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL); |
| if (!bdi) { |
| dev_err(dev, "Can't alloc bdi struct\n"); |
| return -ENOMEM; |
| } |
| bdi->pdata = pdata; |
| bdi->client = client; |
| bdi->dev = dev; |
| bdi->model = id->driver_data; |
| strncpy(bdi->model_name, id->name, sizeof(bdi->model_name)-1); |
| mutex_init(&bdi->bs_reg_lock); |
| bdi->first_time = true; |
| bdi->charger_health_valid = false; |
| bdi->battery_health_valid = false; |
| bdi->battery_status_valid = false; |
| |
| g_bdi = bdi; |
| |
| i2c_set_clientdata(client, bdi); |
| |
| if (dev->of_node) |
| ret = 0;//zx234502_setup_dt(bdi); |
| else |
| ret = zx234502_setup_pdata(bdi, pdata); |
| |
| if (ret) { |
| dev_err(dev, "Can't get irq info\n"); |
| return -EINVAL; |
| } |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO "hwinit.\n"); |
| #endif |
| //pm_runtime_enable(dev); |
| //pm_runtime_resume(dev); |
| i = 0; |
| do{ |
| ret = zx234502_hw_init(bdi); |
| if (ret < 0) { |
| printk(KERN_INFO "zx234502_probe Hardware init failed times = %d\n",(i+1)); |
| i++; |
| } |
| else |
| break; |
| |
| }while(i<3); |
| |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO "zx234502_probe zx234502_hw_init ok.\n"); |
| #endif |
| #ifdef CONFIG_CHARGER_ZX234502_EVB |
| /*set the DCIN Current = 2A*/ |
| ret = zx234502_write_mask(g_bdi,ZX234502_REG_ISC, |
| ZX234502_REG_ISC_ISET_DCIN_MASK, |
| ZX234502_REG_ISC_ISET_DCIN_SHIFT, |
| 0x5); |
| if (ret < 0){ |
| printk(KERN_INFO"write REG_01 fault\n"); |
| } |
| #endif |
| ret = zx234502_charger_config_groupB_Regs(bdi); |
| if (ret < 0) { |
| printk(KERN_INFO "groupB config faild.\n"); |
| } |
| |
| zx234502_charger_init(&bdi->charger); |
| |
| ret = power_supply_register(dev, &bdi->charger); |
| if (ret) { |
| dev_err(dev, "Can't register charger\n"); |
| goto out2; |
| } |
| printk(KERN_INFO "zx234502_probe power_supply_register charger ok.\n"); |
| |
| zx234502_battery_init(&bdi->battery); |
| |
| ret = power_supply_register(dev, &bdi->battery); |
| if (ret) { |
| dev_err(dev, "Can't register battery\n"); |
| goto out3; |
| } |
| printk(KERN_INFO "zx234502_probe power_supply_register battery ok.\n"); |
| |
| if(true == pdata->boost_flag){ |
| zx234502_boost_init(&bdi->boost); |
| ret = power_supply_register(dev, &bdi->boost); |
| if (ret) { |
| dev_err(dev, "Can't register boost\n"); |
| goto out4; |
| } |
| |
| ret = zx234502_boost_gpio_init(dev,pdata); |
| if (ret) { |
| dev_err(dev, "Can't register boost\n"); |
| goto out4; |
| } |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO "zx234502_boost_gpio_init.\n"); |
| #endif |
| |
| bdi->boostQueue = create_workqueue("zx234502boost"); |
| if (!bdi->boostQueue){ |
| if(bdi->pdata->boost_cur_gpio1) |
| gpio_free(bdi->pdata->boost_cur_gpio1); |
| if(bdi->pdata->boost_cur_gpio2) |
| gpio_free(bdi->pdata->boost_cur_gpio2); |
| if(bdi->pdata->boost_cur_gpio3) |
| gpio_free(bdi->pdata->boost_cur_gpio3); |
| if(bdi->pdata->boost_loadswitch_gpio) |
| gpio_free(bdi->pdata->boost_loadswitch_gpio); |
| |
| ret = -1; |
| goto out4; |
| } |
| |
| INIT_DELAYED_WORK(&bdi->boostWorkStruct,zx234502_boost_workstruct_callback); |
| |
| //queue_delayed_work(bdi->boostQueue,&bdi->boostWorkStruct,20000); |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO "setup_workqueue.\n"); |
| #endif |
| } |
| #if 0 |
| //#ifdef CONFIG_SYSFS |
| ret = zx234502_sysfs_create_group(bdi); |
| if (ret) { |
| dev_err(dev, "Can't create sysfs entries\n"); |
| goto out5; |
| } |
| #endif |
| dwc_chg_Regcallback(zx234502_charge_typedet);/*register for usb*/ |
| |
| sema_init(&bdi->chgirq_sem, 0); |
| |
| ret = zx234502_init_state(bdi); |
| if(ret) |
| { |
| dev_err(dev, "zx234502 init state error.\n"); |
| goto out1; |
| } |
| |
| bdi->chg_irq_thread = kthread_run(zx234502_irq_handler_thread, bdi, "zx234502-chgirq"); |
| BUG_ON(IS_ERR(bdi->chg_irq_thread)); |
| |
| ret = request_irq(bdi->irq, zx234502_charger_irq_primary_handler,IRQF_NO_THREAD, "zx234502-charger", bdi); |
| if (ret < 0) { |
| dev_err(dev, "Can't set up irq handler\n"); |
| goto out1; |
| } |
| irq_set_irq_wake(bdi->irq, 1); |
| |
| /*clear all int*/ |
| ret = zx234502_write_mask(bdi,ZX234502_REG_CIIS,ZX234502_REG_CIIS_INT_MASK,ZX234502_REG_CIIS_INT_SHIFT,0x0); |
| if (ret == 0) { |
| printk(KERN_INFO "clear int ok \n"); |
| } |
| |
| |
| #if defined(CONFIG_DEBUG_FS) |
| debugfs_charger_init(bdi); |
| #endif |
| |
| #ifdef DBG_CHARGE |
| printk(KERN_INFO "zx234502_probe end.\n"); |
| #endif |
| |
| return 0; |
| #if 0 |
| out5: |
| zx234502_sysfs_remove_group(bdi); |
| #endif |
| out4: |
| power_supply_unregister(&bdi->boost); |
| out3: |
| power_supply_unregister(&bdi->battery); |
| out2: |
| //pm_runtime_disable(dev); |
| power_supply_unregister(&bdi->charger); |
| out1: |
| if (bdi->gpio_int) |
| gpio_free(bdi->gpio_int); |
| dwc_chg_Regcallback(NULL);/*unregister for usb*/ |
| |
| return ret; |
| } |
| |
| static int zx234502_remove(struct i2c_client *client) |
| { |
| struct zx234502_dev_info *bdi = i2c_get_clientdata(client); |
| struct zx234502_platform_data *pdata = client->dev.platform_data; |
| |
| if(!bdi) |
| return -EINVAL; |
| |
| //pm_runtime_get_sync(bdi->dev); |
| zx234502_register_reset(bdi); |
| //pm_runtime_put_sync(bdi->dev); |
| |
| zx234502_sysfs_remove_group(bdi); |
| power_supply_unregister(&bdi->battery); |
| power_supply_unregister(&bdi->charger); |
| if(true == pdata->boost_flag) |
| power_supply_unregister(&bdi->boost); |
| pm_runtime_disable(bdi->dev); |
| |
| if (bdi->gpio_int) |
| gpio_free(bdi->gpio_int); |
| zx234502_boost_gpio_uninit(pdata); |
| |
| if(bdi->boostQueue) |
| destroy_workqueue(bdi->boostQueue); |
| |
| #if defined(CONFIG_DEBUG_FS) |
| if(g_charger_root){ |
| printk(KERN_INFO "zx234502_device_exit:debugfs_remove_recursive \n"); |
| debugfs_remove_recursive(g_charger_root); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int zx234502_suspend(struct i2c_client *not_use, pm_message_t mesg) |
| { |
| |
| disable_irq_nosync(g_bdi->irq); |
| return 0; |
| |
| } |
| |
| static int zx234502_resume(struct i2c_client *not_use) |
| { |
| |
| enable_irq(g_bdi->irq); |
| return 0; |
| |
| } |
| /* |
| * Only support the zx234502 right now. The bq24192, bq24192i, and bq24193 |
| * are similar but not identical so the driver needs to be extended to |
| * support them. |
| */ |
| static const struct i2c_device_id zx234502_i2c_ids[] = { |
| { "zx234502-charger", ZX234502_REG_VERS }, |
| { }, |
| }; |
| |
| static struct i2c_driver zx234502_driver = { |
| .probe = zx234502_probe, |
| .remove = zx234502_remove, |
| .id_table = zx234502_i2c_ids, |
| .driver = { |
| .name = "zx234502-charger", |
| .owner = THIS_MODULE, |
| }, |
| .suspend = zx234502_suspend, |
| .resume = zx234502_resume, |
| }; |
| static int __init zx234502_i2c_init(void) |
| { |
| int ret; |
| |
| ret = i2c_add_driver(&zx234502_driver); |
| if (ret != 0) |
| pr_err("Failed to register visionox_i2c_driver : %d\n", ret); |
| |
| return ret; |
| } |
| /* init early so consumer devices can complete system boot */ |
| module_init(zx234502_i2c_init); |
| |
| static void __exit zx234502_i2c_exit(void) |
| { |
| i2c_del_driver(&zx234502_driver); |
| } |
| module_exit(zx234502_i2c_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>"); |
| MODULE_ALIAS("i2c:zx234502-charger"); |
| MODULE_DESCRIPTION("TI ZX234502 Charger Driver"); |