Merge branch 'for-mfd-and-power' of git://git.linaro.org/people/ljones/linux-3.0-ux500

From Lee Jones <lee.jones@linaro.org>:

"Please find the next instalment of the AB8500 Power drivers upgrade. A lot of
work has taken place on the internal development track, but little effort has
gone into mainlining it. There is a large backlog of patches which are in need
of forward-porting, then upstreaming. This patch-set aims to make a large dent
into them."

Conflicts:
	drivers/mfd/ab8500-gpadc.c
This commit is contained in:
Anton Vorontsov 2013-03-18 19:02:58 -07:00
commit 441a499e5a
20 changed files with 4465 additions and 956 deletions

View File

@ -95,6 +95,7 @@
#define AB8500_IT_MASK22_REG 0x55 #define AB8500_IT_MASK22_REG 0x55
#define AB8500_IT_MASK23_REG 0x56 #define AB8500_IT_MASK23_REG 0x56
#define AB8500_IT_MASK24_REG 0x57 #define AB8500_IT_MASK24_REG 0x57
#define AB8500_IT_MASK25_REG 0x58
/* /*
* latch hierarchy registers * latch hierarchy registers
@ -102,15 +103,25 @@
#define AB8500_IT_LATCHHIER1_REG 0x60 #define AB8500_IT_LATCHHIER1_REG 0x60
#define AB8500_IT_LATCHHIER2_REG 0x61 #define AB8500_IT_LATCHHIER2_REG 0x61
#define AB8500_IT_LATCHHIER3_REG 0x62 #define AB8500_IT_LATCHHIER3_REG 0x62
#define AB8540_IT_LATCHHIER4_REG 0x63
#define AB8500_IT_LATCHHIER_NUM 3 #define AB8500_IT_LATCHHIER_NUM 3
#define AB8540_IT_LATCHHIER_NUM 4
#define AB8500_REV_REG 0x80 #define AB8500_REV_REG 0x80
#define AB8500_IC_NAME_REG 0x82 #define AB8500_IC_NAME_REG 0x82
#define AB8500_SWITCH_OFF_STATUS 0x00 #define AB8500_SWITCH_OFF_STATUS 0x00
#define AB8500_TURN_ON_STATUS 0x00 #define AB8500_TURN_ON_STATUS 0x00
#define AB8505_TURN_ON_STATUS_2 0x04
#define AB8500_CH_USBCH_STAT1_REG 0x02
#define VBUS_DET_DBNC100 0x02
#define VBUS_DET_DBNC1 0x01
static DEFINE_SPINLOCK(on_stat_lock);
static u8 turn_on_stat_mask = 0xFF;
static u8 turn_on_stat_set;
static bool no_bm; /* No battery management */ static bool no_bm; /* No battery management */
module_param(no_bm, bool, S_IRUGO); module_param(no_bm, bool, S_IRUGO);
@ -130,9 +141,15 @@ static const int ab8500_irq_regoffset[AB8500_NUM_IRQ_REGS] = {
0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21,
}; };
/* AB9540 support */ /* AB9540 / AB8505 support */
static const int ab9540_irq_regoffset[AB9540_NUM_IRQ_REGS] = { static const int ab9540_irq_regoffset[AB9540_NUM_IRQ_REGS] = {
0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 12, 13, 24, 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, 23
};
/* AB8540 support */
static const int ab8540_irq_regoffset[AB8540_NUM_IRQ_REGS] = {
0, 1, 2, 3, 4, -1, -1, -1, -1, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, 23,
25, 26, 27, 28, 29, 30, 31,
}; };
static const char ab8500_version_str[][7] = { static const char ab8500_version_str[][7] = {
@ -352,6 +369,9 @@ static void ab8500_irq_sync_unlock(struct irq_data *data)
is_ab8500_1p1_or_earlier(ab8500)) is_ab8500_1p1_or_earlier(ab8500))
continue; continue;
if (ab8500->irq_reg_offset[i] < 0)
continue;
ab8500->oldmask[i] = new; ab8500->oldmask[i] = new;
reg = AB8500_IT_MASK1_REG + ab8500->irq_reg_offset[i]; reg = AB8500_IT_MASK1_REG + ab8500->irq_reg_offset[i];
@ -423,6 +443,18 @@ static struct irq_chip ab8500_irq_chip = {
.irq_set_type = ab8500_irq_set_type, .irq_set_type = ab8500_irq_set_type,
}; };
static void update_latch_offset(u8 *offset, int i)
{
/* Fix inconsistent ITFromLatch25 bit mapping... */
if (unlikely(*offset == 17))
*offset = 24;
/* Fix inconsistent ab8540 bit mapping... */
if (unlikely(*offset == 16))
*offset = 25;
if ((i==3) && (*offset >= 24))
*offset += 2;
}
static int ab8500_handle_hierarchical_line(struct ab8500 *ab8500, static int ab8500_handle_hierarchical_line(struct ab8500 *ab8500,
int latch_offset, u8 latch_val) int latch_offset, u8 latch_val)
{ {
@ -474,9 +506,7 @@ static int ab8500_handle_hierarchical_latch(struct ab8500 *ab8500,
latch_bit = __ffs(hier_val); latch_bit = __ffs(hier_val);
latch_offset = (hier_offset << 3) + latch_bit; latch_offset = (hier_offset << 3) + latch_bit;
/* Fix inconsistent ITFromLatch25 bit mapping... */ update_latch_offset(&latch_offset, hier_offset);
if (unlikely(latch_offset == 17))
latch_offset = 24;
status = get_register_interruptible(ab8500, status = get_register_interruptible(ab8500,
AB8500_INTERRUPT, AB8500_INTERRUPT,
@ -504,7 +534,7 @@ static irqreturn_t ab8500_hierarchical_irq(int irq, void *dev)
dev_vdbg(ab8500->dev, "interrupt\n"); dev_vdbg(ab8500->dev, "interrupt\n");
/* Hierarchical interrupt version */ /* Hierarchical interrupt version */
for (i = 0; i < AB8500_IT_LATCHHIER_NUM; i++) { for (i = 0; i < (ab8500->it_latchhier_num); i++) {
int status; int status;
u8 hier_val; u8 hier_val;
@ -520,63 +550,6 @@ static irqreturn_t ab8500_hierarchical_irq(int irq, void *dev)
return IRQ_HANDLED; return IRQ_HANDLED;
} }
/**
* ab8500_irq_get_virq(): Map an interrupt on a chip to a virtual IRQ
*
* @ab8500: ab8500_irq controller to operate on.
* @irq: index of the interrupt requested in the chip IRQs
*
* Useful for drivers to request their own IRQs.
*/
static int ab8500_irq_get_virq(struct ab8500 *ab8500, int irq)
{
if (!ab8500)
return -EINVAL;
return irq_create_mapping(ab8500->domain, irq);
}
static irqreturn_t ab8500_irq(int irq, void *dev)
{
struct ab8500 *ab8500 = dev;
int i;
dev_vdbg(ab8500->dev, "interrupt\n");
atomic_inc(&ab8500->transfer_ongoing);
for (i = 0; i < ab8500->mask_size; i++) {
int regoffset = ab8500->irq_reg_offset[i];
int status;
u8 value;
/*
* Interrupt register 12 doesn't exist prior to AB8500 version
* 2.0
*/
if (regoffset == 11 && is_ab8500_1p1_or_earlier(ab8500))
continue;
status = get_register_interruptible(ab8500, AB8500_INTERRUPT,
AB8500_IT_LATCH1_REG + regoffset, &value);
if (status < 0 || value == 0)
continue;
do {
int bit = __ffs(value);
int line = i * 8 + bit;
int virq = ab8500_irq_get_virq(ab8500, line);
handle_nested_irq(virq);
ab8500_debug_register_interrupt(line);
value &= ~(1 << bit);
} while (value);
}
atomic_dec(&ab8500->transfer_ongoing);
return IRQ_HANDLED;
}
static int ab8500_irq_map(struct irq_domain *d, unsigned int virq, static int ab8500_irq_map(struct irq_domain *d, unsigned int virq,
irq_hw_number_t hwirq) irq_hw_number_t hwirq)
{ {
@ -607,7 +580,9 @@ static int ab8500_irq_init(struct ab8500 *ab8500, struct device_node *np)
{ {
int num_irqs; int num_irqs;
if (is_ab9540(ab8500)) if (is_ab8540(ab8500))
num_irqs = AB8540_NR_IRQS;
else if (is_ab9540(ab8500))
num_irqs = AB9540_NR_IRQS; num_irqs = AB9540_NR_IRQS;
else if (is_ab8505(ab8500)) else if (is_ab8505(ab8500))
num_irqs = AB8505_NR_IRQS; num_irqs = AB8505_NR_IRQS;
@ -650,6 +625,15 @@ static struct resource ab8500_gpadc_resources[] = {
}, },
}; };
static struct resource ab8505_gpadc_resources[] = {
{
.name = "SW_CONV_END",
.start = AB8500_INT_GP_SW_ADC_CONV_END,
.end = AB8500_INT_GP_SW_ADC_CONV_END,
.flags = IORESOURCE_IRQ,
},
};
static struct resource ab8500_rtc_resources[] = { static struct resource ab8500_rtc_resources[] = {
{ {
.name = "60S", .name = "60S",
@ -973,6 +957,30 @@ static struct resource ab8505_iddet_resources[] = {
.end = AB8505_INT_KEYSTUCK, .end = AB8505_INT_KEYSTUCK,
.flags = IORESOURCE_IRQ, .flags = IORESOURCE_IRQ,
}, },
{
.name = "VBUS_DET_R",
.start = AB8500_INT_VBUS_DET_R,
.end = AB8500_INT_VBUS_DET_R,
.flags = IORESOURCE_IRQ,
},
{
.name = "VBUS_DET_F",
.start = AB8500_INT_VBUS_DET_F,
.end = AB8500_INT_VBUS_DET_F,
.flags = IORESOURCE_IRQ,
},
{
.name = "ID_DET_PLUGR",
.start = AB8500_INT_ID_DET_PLUGR,
.end = AB8500_INT_ID_DET_PLUGR,
.flags = IORESOURCE_IRQ,
},
{
.name = "ID_DET_PLUGF",
.start = AB8500_INT_ID_DET_PLUGF,
.end = AB8500_INT_ID_DET_PLUGF,
.flags = IORESOURCE_IRQ,
},
}; };
static struct resource ab8500_temp_resources[] = { static struct resource ab8500_temp_resources[] = {
@ -984,82 +992,6 @@ static struct resource ab8500_temp_resources[] = {
}, },
}; };
static struct mfd_cell abx500_common_devs[] = {
#ifdef CONFIG_DEBUG_FS
{
.name = "ab8500-debug",
.of_compatible = "stericsson,ab8500-debug",
.num_resources = ARRAY_SIZE(ab8500_debug_resources),
.resources = ab8500_debug_resources,
},
#endif
{
.name = "ab8500-sysctrl",
.of_compatible = "stericsson,ab8500-sysctrl",
},
{
.name = "ab8500-regulator",
.of_compatible = "stericsson,ab8500-regulator",
},
{
.name = "abx500-clk",
.of_compatible = "stericsson,abx500-clk",
},
{
.name = "ab8500-gpadc",
.of_compatible = "stericsson,ab8500-gpadc",
.num_resources = ARRAY_SIZE(ab8500_gpadc_resources),
.resources = ab8500_gpadc_resources,
},
{
.name = "ab8500-rtc",
.of_compatible = "stericsson,ab8500-rtc",
.num_resources = ARRAY_SIZE(ab8500_rtc_resources),
.resources = ab8500_rtc_resources,
},
{
.name = "ab8500-acc-det",
.of_compatible = "stericsson,ab8500-acc-det",
.num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources),
.resources = ab8500_av_acc_detect_resources,
},
{
.name = "ab8500-poweron-key",
.of_compatible = "stericsson,ab8500-poweron-key",
.num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources),
.resources = ab8500_poweronkey_db_resources,
},
{
.name = "ab8500-pwm",
.of_compatible = "stericsson,ab8500-pwm",
.id = 1,
},
{
.name = "ab8500-pwm",
.of_compatible = "stericsson,ab8500-pwm",
.id = 2,
},
{
.name = "ab8500-pwm",
.of_compatible = "stericsson,ab8500-pwm",
.id = 3,
},
{
.name = "ab8500-leds",
.of_compatible = "stericsson,ab8500-leds",
},
{
.name = "ab8500-denc",
.of_compatible = "stericsson,ab8500-denc",
},
{
.name = "abx500-temp",
.of_compatible = "stericsson,abx500-temp",
.num_resources = ARRAY_SIZE(ab8500_temp_resources),
.resources = ab8500_temp_resources,
},
};
static struct mfd_cell ab8500_bm_devs[] = { static struct mfd_cell ab8500_bm_devs[] = {
{ {
.name = "ab8500-charger", .name = "ab8500-charger",
@ -1096,23 +1028,144 @@ static struct mfd_cell ab8500_bm_devs[] = {
}; };
static struct mfd_cell ab8500_devs[] = { static struct mfd_cell ab8500_devs[] = {
#ifdef CONFIG_DEBUG_FS
{ {
.name = "pinctrl-ab8500", .name = "ab8500-debug",
.of_compatible = "stericsson,ab8500-debug",
.num_resources = ARRAY_SIZE(ab8500_debug_resources),
.resources = ab8500_debug_resources,
},
#endif
{
.name = "ab8500-sysctrl",
.of_compatible = "stericsson,ab8500-sysctrl",
},
{
.name = "ab8500-regulator",
.of_compatible = "stericsson,ab8500-regulator",
},
{
.name = "abx500-clk",
.of_compatible = "stericsson,abx500-clk",
},
{
.name = "ab8500-gpadc",
.num_resources = ARRAY_SIZE(ab8500_gpadc_resources),
.resources = ab8500_gpadc_resources,
},
{
.name = "ab8500-rtc",
.of_compatible = "stericsson,ab8500-rtc",
.num_resources = ARRAY_SIZE(ab8500_rtc_resources),
.resources = ab8500_rtc_resources,
},
{
.name = "ab8500-acc-det",
.of_compatible = "stericsson,ab8500-acc-det",
.num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources),
.resources = ab8500_av_acc_detect_resources,
},
{
.name = "ab8500-poweron-key",
.of_compatible = "stericsson,ab8500-poweron-key",
.num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources),
.resources = ab8500_poweronkey_db_resources,
},
{
.name = "ab8500-pwm",
.of_compatible = "stericsson,ab8500-pwm",
.id = 1,
},
{
.name = "ab8500-pwm",
.of_compatible = "stericsson,ab8500-pwm",
.id = 2,
},
{
.name = "ab8500-pwm",
.of_compatible = "stericsson,ab8500-pwm",
.id = 3,
},
{
.name = "ab8500-leds",
.of_compatible = "stericsson,ab8500-leds",
},
{
.name = "ab8500-denc",
.of_compatible = "stericsson,ab8500-denc",
},
{
.name = "ab8500-gpio",
.of_compatible = "stericsson,ab8500-gpio", .of_compatible = "stericsson,ab8500-gpio",
}, },
{
.name = "abx500-temp",
.of_compatible = "stericsson,abx500-temp",
.num_resources = ARRAY_SIZE(ab8500_temp_resources),
.resources = ab8500_temp_resources,
},
{ {
.name = "ab8500-usb", .name = "ab8500-usb",
.of_compatible = "stericsson,ab8500-usb",
.num_resources = ARRAY_SIZE(ab8500_usb_resources), .num_resources = ARRAY_SIZE(ab8500_usb_resources),
.resources = ab8500_usb_resources, .resources = ab8500_usb_resources,
}, },
{ {
.name = "ab8500-codec", .name = "ab8500-codec",
.of_compatible = "stericsson,ab8500-codec",
}, },
}; };
static struct mfd_cell ab9540_devs[] = { static struct mfd_cell ab9540_devs[] = {
#ifdef CONFIG_DEBUG_FS
{
.name = "ab8500-debug",
.num_resources = ARRAY_SIZE(ab8500_debug_resources),
.resources = ab8500_debug_resources,
},
#endif
{
.name = "ab8500-sysctrl",
},
{
.name = "ab8500-regulator",
},
{
.name = "abx500-clk",
.of_compatible = "stericsson,abx500-clk",
},
{
.name = "ab8500-gpadc",
.of_compatible = "stericsson,ab8500-gpadc",
.num_resources = ARRAY_SIZE(ab8500_gpadc_resources),
.resources = ab8500_gpadc_resources,
},
{
.name = "ab8500-rtc",
.num_resources = ARRAY_SIZE(ab8500_rtc_resources),
.resources = ab8500_rtc_resources,
},
{
.name = "ab8500-acc-det",
.num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources),
.resources = ab8500_av_acc_detect_resources,
},
{
.name = "ab8500-poweron-key",
.num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources),
.resources = ab8500_poweronkey_db_resources,
},
{
.name = "ab8500-pwm",
.id = 1,
},
{
.name = "ab8500-leds",
},
{
.name = "abx500-temp",
.num_resources = ARRAY_SIZE(ab8500_temp_resources),
.resources = ab8500_temp_resources,
},
{ {
.name = "pinctrl-ab9540", .name = "pinctrl-ab9540",
.of_compatible = "stericsson,ab9540-gpio", .of_compatible = "stericsson,ab9540-gpio",
@ -1125,10 +1178,138 @@ static struct mfd_cell ab9540_devs[] = {
{ {
.name = "ab9540-codec", .name = "ab9540-codec",
}, },
{
.name = "ab-iddet",
.num_resources = ARRAY_SIZE(ab8505_iddet_resources),
.resources = ab8505_iddet_resources,
},
}; };
/* Device list common to ab9540 and ab8505 */ /* Device list for ab8505 */
static struct mfd_cell ab9540_ab8505_devs[] = { static struct mfd_cell ab8505_devs[] = {
#ifdef CONFIG_DEBUG_FS
{
.name = "ab8500-debug",
.num_resources = ARRAY_SIZE(ab8500_debug_resources),
.resources = ab8500_debug_resources,
},
#endif
{
.name = "ab8500-sysctrl",
},
{
.name = "ab8500-regulator",
},
{
.name = "abx500-clk",
.of_compatible = "stericsson,abx500-clk",
},
{
.name = "ab8500-gpadc",
.num_resources = ARRAY_SIZE(ab8505_gpadc_resources),
.resources = ab8505_gpadc_resources,
},
{
.name = "ab8500-rtc",
.num_resources = ARRAY_SIZE(ab8500_rtc_resources),
.resources = ab8500_rtc_resources,
},
{
.name = "ab8500-acc-det",
.num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources),
.resources = ab8500_av_acc_detect_resources,
},
{
.name = "ab8500-poweron-key",
.num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources),
.resources = ab8500_poweronkey_db_resources,
},
{
.name = "ab8500-pwm",
.id = 1,
},
{
.name = "ab8500-leds",
},
{
.name = "ab8500-gpio",
},
{
.name = "ab8500-usb",
.num_resources = ARRAY_SIZE(ab8500_usb_resources),
.resources = ab8500_usb_resources,
},
{
.name = "ab8500-codec",
},
{
.name = "ab-iddet",
.num_resources = ARRAY_SIZE(ab8505_iddet_resources),
.resources = ab8505_iddet_resources,
},
};
static struct mfd_cell ab8540_devs[] = {
#ifdef CONFIG_DEBUG_FS
{
.name = "ab8500-debug",
.num_resources = ARRAY_SIZE(ab8500_debug_resources),
.resources = ab8500_debug_resources,
},
#endif
{
.name = "ab8500-sysctrl",
},
{
.name = "ab8500-regulator",
},
{
.name = "abx500-clk",
.of_compatible = "stericsson,abx500-clk",
},
{
.name = "ab8500-gpadc",
.num_resources = ARRAY_SIZE(ab8505_gpadc_resources),
.resources = ab8505_gpadc_resources,
},
{
.name = "ab8500-rtc",
.num_resources = ARRAY_SIZE(ab8500_rtc_resources),
.resources = ab8500_rtc_resources,
},
{
.name = "ab8500-acc-det",
.num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources),
.resources = ab8500_av_acc_detect_resources,
},
{
.name = "ab8500-poweron-key",
.num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources),
.resources = ab8500_poweronkey_db_resources,
},
{
.name = "ab8500-pwm",
.id = 1,
},
{
.name = "ab8500-leds",
},
{
.name = "abx500-temp",
.num_resources = ARRAY_SIZE(ab8500_temp_resources),
.resources = ab8500_temp_resources,
},
{
.name = "ab8500-gpio",
},
{
.name = "ab8540-usb",
.num_resources = ARRAY_SIZE(ab8500_usb_resources),
.resources = ab8500_usb_resources,
},
{
.name = "ab8540-codec",
},
{ {
.name = "ab-iddet", .name = "ab-iddet",
.num_resources = ARRAY_SIZE(ab8505_iddet_resources), .num_resources = ARRAY_SIZE(ab8505_iddet_resources),
@ -1142,6 +1323,7 @@ static ssize_t show_chip_id(struct device *dev,
struct ab8500 *ab8500; struct ab8500 *ab8500;
ab8500 = dev_get_drvdata(dev); ab8500 = dev_get_drvdata(dev);
return sprintf(buf, "%#x\n", ab8500 ? ab8500->chip_id : -EINVAL); return sprintf(buf, "%#x\n", ab8500 ? ab8500->chip_id : -EINVAL);
} }
@ -1171,6 +1353,15 @@ static ssize_t show_switch_off_status(struct device *dev,
return sprintf(buf, "%#x\n", value); return sprintf(buf, "%#x\n", value);
} }
/* use mask and set to override the register turn_on_stat value */
void ab8500_override_turn_on_stat(u8 mask, u8 set)
{
spin_lock(&on_stat_lock);
turn_on_stat_mask = mask;
turn_on_stat_set = set;
spin_unlock(&on_stat_lock);
}
/* /*
* ab8500 has turned on due to (TURN_ON_STATUS): * ab8500 has turned on due to (TURN_ON_STATUS):
* 0x01 PORnVbat * 0x01 PORnVbat
@ -1194,9 +1385,38 @@ static ssize_t show_turn_on_status(struct device *dev,
AB8500_TURN_ON_STATUS, &value); AB8500_TURN_ON_STATUS, &value);
if (ret < 0) if (ret < 0)
return ret; return ret;
/*
* In L9540, turn_on_status register is not updated correctly if
* the device is rebooted with AC/USB charger connected. Due to
* this, the device boots android instead of entering into charge
* only mode. Read the AC/USB status register to detect the charger
* presence and update the turn on status manually.
*/
if (is_ab9540(ab8500)) {
spin_lock(&on_stat_lock);
value = (value & turn_on_stat_mask) | turn_on_stat_set;
spin_unlock(&on_stat_lock);
}
return sprintf(buf, "%#x\n", value); return sprintf(buf, "%#x\n", value);
} }
static ssize_t show_turn_on_status_2(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
u8 value;
struct ab8500 *ab8500;
ab8500 = dev_get_drvdata(dev);
ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK,
AB8505_TURN_ON_STATUS_2, &value);
if (ret < 0)
return ret;
return sprintf(buf, "%#x\n", (value & 0x1));
}
static ssize_t show_ab9540_dbbrstn(struct device *dev, static ssize_t show_ab9540_dbbrstn(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
@ -1253,6 +1473,7 @@ exit:
static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL);
static DEVICE_ATTR(switch_off_status, S_IRUGO, show_switch_off_status, NULL); static DEVICE_ATTR(switch_off_status, S_IRUGO, show_switch_off_status, NULL);
static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL);
static DEVICE_ATTR(turn_on_status_2, S_IRUGO, show_turn_on_status_2, NULL);
static DEVICE_ATTR(dbbrstn, S_IRUGO | S_IWUSR, static DEVICE_ATTR(dbbrstn, S_IRUGO | S_IWUSR,
show_ab9540_dbbrstn, store_ab9540_dbbrstn); show_ab9540_dbbrstn, store_ab9540_dbbrstn);
@ -1263,6 +1484,11 @@ static struct attribute *ab8500_sysfs_entries[] = {
NULL, NULL,
}; };
static struct attribute *ab8505_sysfs_entries[] = {
&dev_attr_turn_on_status_2.attr,
NULL,
};
static struct attribute *ab9540_sysfs_entries[] = { static struct attribute *ab9540_sysfs_entries[] = {
&dev_attr_chip_id.attr, &dev_attr_chip_id.attr,
&dev_attr_switch_off_status.attr, &dev_attr_switch_off_status.attr,
@ -1275,6 +1501,10 @@ static struct attribute_group ab8500_attr_group = {
.attrs = ab8500_sysfs_entries, .attrs = ab8500_sysfs_entries,
}; };
static struct attribute_group ab8505_attr_group = {
.attrs = ab8505_sysfs_entries,
};
static struct attribute_group ab9540_attr_group = { static struct attribute_group ab9540_attr_group = {
.attrs = ab9540_sysfs_entries, .attrs = ab9540_sysfs_entries,
}; };
@ -1290,6 +1520,15 @@ static int ab8500_probe(struct platform_device *pdev)
"Battery level lower than power on reset threshold", "Battery level lower than power on reset threshold",
"Power on key 1 pressed longer than 10 seconds", "Power on key 1 pressed longer than 10 seconds",
"DB8500 thermal shutdown"}; "DB8500 thermal shutdown"};
static char *turn_on_status[] = {
"Battery rising (Vbat)",
"Power On Key 1 dbF",
"Power On Key 2 dbF",
"RTC Alarm",
"Main Charger Detect",
"Vbus Detect (USB)",
"USB ID Detect",
"UART Factory Mode Detect"};
struct ab8500_platform_data *plat = dev_get_platdata(&pdev->dev); struct ab8500_platform_data *plat = dev_get_platdata(&pdev->dev);
const struct platform_device_id *platid = platform_get_device_id(pdev); const struct platform_device_id *platid = platform_get_device_id(pdev);
enum ab8500_version version = AB8500_VERSION_UNDEFINED; enum ab8500_version version = AB8500_VERSION_UNDEFINED;
@ -1351,13 +1590,20 @@ static int ab8500_probe(struct platform_device *pdev)
ab8500->chip_id >> 4, ab8500->chip_id >> 4,
ab8500->chip_id & 0x0F); ab8500->chip_id & 0x0F);
/* Configure AB8500 or AB9540 IRQ */ /* Configure AB8540 */
if (is_ab9540(ab8500) || is_ab8505(ab8500)) { if (is_ab8540(ab8500)) {
ab8500->mask_size = AB8540_NUM_IRQ_REGS;
ab8500->irq_reg_offset = ab8540_irq_regoffset;
ab8500->it_latchhier_num = AB8540_IT_LATCHHIER_NUM;
}/* Configure AB8500 or AB9540 IRQ */
else if (is_ab9540(ab8500) || is_ab8505(ab8500)) {
ab8500->mask_size = AB9540_NUM_IRQ_REGS; ab8500->mask_size = AB9540_NUM_IRQ_REGS;
ab8500->irq_reg_offset = ab9540_irq_regoffset; ab8500->irq_reg_offset = ab9540_irq_regoffset;
ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM;
} else { } else {
ab8500->mask_size = AB8500_NUM_IRQ_REGS; ab8500->mask_size = AB8500_NUM_IRQ_REGS;
ab8500->irq_reg_offset = ab8500_irq_regoffset; ab8500->irq_reg_offset = ab8500_irq_regoffset;
ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM;
} }
ab8500->mask = devm_kzalloc(&pdev->dev, ab8500->mask_size, GFP_KERNEL); ab8500->mask = devm_kzalloc(&pdev->dev, ab8500->mask_size, GFP_KERNEL);
if (!ab8500->mask) if (!ab8500->mask)
@ -1396,10 +1642,36 @@ static int ab8500_probe(struct platform_device *pdev)
} else { } else {
printk(KERN_CONT " None\n"); printk(KERN_CONT " None\n");
} }
ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK,
AB8500_TURN_ON_STATUS, &value);
if (ret < 0)
return ret;
dev_info(ab8500->dev, "turn on reason(s) (%#x): ", value);
if (value) {
for (i = 0; i < ARRAY_SIZE(turn_on_status); i++) {
if (value & 1)
printk("\"%s\" ", turn_on_status[i]);
value = value >> 1;
}
printk("\n");
} else {
printk("None\n");
}
if (plat && plat->init) if (plat && plat->init)
plat->init(ab8500); plat->init(ab8500);
if (is_ab9540(ab8500)) {
ret = get_register_interruptible(ab8500, AB8500_CHARGER,
AB8500_CH_USBCH_STAT1_REG, &value);
if (ret < 0)
return ret;
if ((value & VBUS_DET_DBNC1) && (value & VBUS_DET_DBNC100))
ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON,
AB8500_VBUS_DET);
}
/* Clear and mask all interrupts */ /* Clear and mask all interrupts */
for (i = 0; i < ab8500->mask_size; i++) { for (i = 0; i < ab8500->mask_size; i++) {
/* /*
@ -1410,6 +1682,9 @@ static int ab8500_probe(struct platform_device *pdev)
is_ab8500_1p1_or_earlier(ab8500)) is_ab8500_1p1_or_earlier(ab8500))
continue; continue;
if (ab8500->irq_reg_offset[i] < 0)
continue;
get_register_interruptible(ab8500, AB8500_INTERRUPT, get_register_interruptible(ab8500, AB8500_INTERRUPT,
AB8500_IT_LATCH1_REG + ab8500->irq_reg_offset[i], AB8500_IT_LATCH1_REG + ab8500->irq_reg_offset[i],
&value); &value);
@ -1428,26 +1703,10 @@ static int ab8500_probe(struct platform_device *pdev)
if (ret) if (ret)
return ret; return ret;
/* Activate this feature only in ab9540 */ ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL,
/* till tests are done on ab8500 1p2 or later*/ ab8500_hierarchical_irq,
if (is_ab9540(ab8500)) { IRQF_ONESHOT | IRQF_NO_SUSPEND,
ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, "ab8500", ab8500);
ab8500_hierarchical_irq,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
"ab8500", ab8500);
}
else {
ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL,
ab8500_irq,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
"ab8500", ab8500);
if (ret)
return ret;
}
ret = mfd_add_devices(ab8500->dev, 0, abx500_common_devs,
ARRAY_SIZE(abx500_common_devs), NULL,
ab8500->irq_base, ab8500->domain);
if (ret) if (ret)
return ret; return ret;
@ -1455,6 +1714,14 @@ static int ab8500_probe(struct platform_device *pdev)
ret = mfd_add_devices(ab8500->dev, 0, ab9540_devs, ret = mfd_add_devices(ab8500->dev, 0, ab9540_devs,
ARRAY_SIZE(ab9540_devs), NULL, ARRAY_SIZE(ab9540_devs), NULL,
ab8500->irq_base, ab8500->domain); ab8500->irq_base, ab8500->domain);
else if (is_ab8540(ab8500))
ret = mfd_add_devices(ab8500->dev, 0, ab8540_devs,
ARRAY_SIZE(ab8540_devs), NULL,
ab8500->irq_base, ab8500->domain);
else if (is_ab8505(ab8500))
ret = mfd_add_devices(ab8500->dev, 0, ab8505_devs,
ARRAY_SIZE(ab8505_devs), NULL,
ab8500->irq_base, ab8500->domain);
else else
ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs, ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs,
ARRAY_SIZE(ab8500_devs), NULL, ARRAY_SIZE(ab8500_devs), NULL,
@ -1462,13 +1729,6 @@ static int ab8500_probe(struct platform_device *pdev)
if (ret) if (ret)
return ret; return ret;
if (is_ab9540(ab8500) || is_ab8505(ab8500))
ret = mfd_add_devices(ab8500->dev, 0, ab9540_ab8505_devs,
ARRAY_SIZE(ab9540_ab8505_devs), NULL,
ab8500->irq_base, ab8500->domain);
if (ret)
return ret;
if (!no_bm) { if (!no_bm) {
/* Add battery management devices */ /* Add battery management devices */
ret = mfd_add_devices(ab8500->dev, 0, ab8500_bm_devs, ret = mfd_add_devices(ab8500->dev, 0, ab8500_bm_devs,
@ -1478,12 +1738,19 @@ static int ab8500_probe(struct platform_device *pdev)
dev_err(ab8500->dev, "error adding bm devices\n"); dev_err(ab8500->dev, "error adding bm devices\n");
} }
if (is_ab9540(ab8500)) if (((is_ab8505(ab8500) || is_ab9540(ab8500)) &&
ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500))
ret = sysfs_create_group(&ab8500->dev->kobj, ret = sysfs_create_group(&ab8500->dev->kobj,
&ab9540_attr_group); &ab9540_attr_group);
else else
ret = sysfs_create_group(&ab8500->dev->kobj, ret = sysfs_create_group(&ab8500->dev->kobj,
&ab8500_attr_group); &ab8500_attr_group);
if ((is_ab8505(ab8500) || is_ab9540(ab8500)) &&
ab8500->chip_id >= AB8500_CUT2P0)
ret = sysfs_create_group(&ab8500->dev->kobj,
&ab8505_attr_group);
if (ret) if (ret)
dev_err(ab8500->dev, "error creating sysfs entries\n"); dev_err(ab8500->dev, "error creating sysfs entries\n");
@ -1494,11 +1761,16 @@ static int ab8500_remove(struct platform_device *pdev)
{ {
struct ab8500 *ab8500 = platform_get_drvdata(pdev); struct ab8500 *ab8500 = platform_get_drvdata(pdev);
if (is_ab9540(ab8500)) if (((is_ab8505(ab8500) || is_ab9540(ab8500)) &&
ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500))
sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group); sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group);
else else
sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group); sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group);
if ((is_ab8505(ab8500) || is_ab9540(ab8500)) &&
ab8500->chip_id >= AB8500_CUT2P0)
sysfs_remove_group(&ab8500->dev->kobj, &ab8505_attr_group);
mfd_remove_devices(ab8500->dev); mfd_remove_devices(ab8500->dev);
return 0; return 0;

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,13 @@
#define AB8500_GPADC_AUTODATAL_REG 0x07 #define AB8500_GPADC_AUTODATAL_REG 0x07
#define AB8500_GPADC_AUTODATAH_REG 0x08 #define AB8500_GPADC_AUTODATAH_REG 0x08
#define AB8500_GPADC_MUX_CTRL_REG 0x09 #define AB8500_GPADC_MUX_CTRL_REG 0x09
#define AB8540_GPADC_MANDATA2L_REG 0x09
#define AB8540_GPADC_MANDATA2H_REG 0x0A
#define AB8540_GPADC_APEAAX_REG 0x10
#define AB8540_GPADC_APEAAT_REG 0x11
#define AB8540_GPADC_APEAAM_REG 0x12
#define AB8540_GPADC_APEAAH_REG 0x13
#define AB8540_GPADC_APEAAL_REG 0x14
/* /*
* OTP register offsets * OTP register offsets
@ -49,19 +56,29 @@
#define AB8500_GPADC_CAL_5 0x13 #define AB8500_GPADC_CAL_5 0x13
#define AB8500_GPADC_CAL_6 0x14 #define AB8500_GPADC_CAL_6 0x14
#define AB8500_GPADC_CAL_7 0x15 #define AB8500_GPADC_CAL_7 0x15
/* New calibration for 8540 */
#define AB8540_GPADC_OTP4_REG_7 0x38
#define AB8540_GPADC_OTP4_REG_6 0x39
#define AB8540_GPADC_OTP4_REG_5 0x3A
/* gpadc constants */ /* gpadc constants */
#define EN_VINTCORE12 0x04 #define EN_VINTCORE12 0x04
#define EN_VTVOUT 0x02 #define EN_VTVOUT 0x02
#define EN_GPADC 0x01 #define EN_GPADC 0x01
#define DIS_GPADC 0x00 #define DIS_GPADC 0x00
#define SW_AVG_16 0x60 #define AVG_1 0x00
#define AVG_4 0x20
#define AVG_8 0x40
#define AVG_16 0x60
#define ADC_SW_CONV 0x04 #define ADC_SW_CONV 0x04
#define EN_ICHAR 0x80 #define EN_ICHAR 0x80
#define BTEMP_PULL_UP 0x08 #define BTEMP_PULL_UP 0x08
#define EN_BUF 0x40 #define EN_BUF 0x40
#define DIS_ZERO 0x00 #define DIS_ZERO 0x00
#define GPADC_BUSY 0x01 #define GPADC_BUSY 0x01
#define EN_FALLING 0x10
#define EN_TRIG_EDGE 0x02
#define EN_VBIAS_XTAL_TEMP 0x02
/* GPADC constants from AB8500 spec, UM0836 */ /* GPADC constants from AB8500 spec, UM0836 */
#define ADC_RESOLUTION 1024 #define ADC_RESOLUTION 1024
@ -80,8 +97,21 @@
#define ADC_CH_BKBAT_MIN 0 #define ADC_CH_BKBAT_MIN 0
#define ADC_CH_BKBAT_MAX 3200 #define ADC_CH_BKBAT_MAX 3200
/* GPADC constants from AB8540 spec */
#define ADC_CH_IBAT_MIN (-6000) /* mA range measured by ADC for ibat*/
#define ADC_CH_IBAT_MAX 6000
#define ADC_CH_IBAT_MIN_V (-60) /* mV range measured by ADC for ibat*/
#define ADC_CH_IBAT_MAX_V 60
#define IBAT_VDROP_L (-56) /* mV */
#define IBAT_VDROP_H 56
/* This is used to not lose precision when dividing to get gain and offset */ /* This is used to not lose precision when dividing to get gain and offset */
#define CALIB_SCALE 1000 #define CALIB_SCALE 1000
/*
* Number of bits shift used to not lose precision
* when dividing to get ibat gain.
*/
#define CALIB_SHIFT_IBAT 20
/* Time in ms before disabling regulator */ /* Time in ms before disabling regulator */
#define GPADC_AUDOSUSPEND_DELAY 1 #define GPADC_AUDOSUSPEND_DELAY 1
@ -92,6 +122,7 @@ enum cal_channels {
ADC_INPUT_VMAIN = 0, ADC_INPUT_VMAIN = 0,
ADC_INPUT_BTEMP, ADC_INPUT_BTEMP,
ADC_INPUT_VBAT, ADC_INPUT_VBAT,
ADC_INPUT_IBAT,
NBR_CAL_INPUTS, NBR_CAL_INPUTS,
}; };
@ -102,8 +133,10 @@ enum cal_channels {
* @offset: Offset of the ADC channel * @offset: Offset of the ADC channel
*/ */
struct adc_cal_data { struct adc_cal_data {
u64 gain; s64 gain;
u64 offset; s64 offset;
u16 otp_calib_hi;
u16 otp_calib_lo;
}; };
/** /**
@ -116,7 +149,10 @@ struct adc_cal_data {
* the completion of gpadc conversion * the completion of gpadc conversion
* @ab8500_gpadc_lock: structure of type mutex * @ab8500_gpadc_lock: structure of type mutex
* @regu: pointer to the struct regulator * @regu: pointer to the struct regulator
* @irq: interrupt number that is used by gpadc * @irq_sw: interrupt number that is used by gpadc for Sw
* conversion
* @irq_hw: interrupt number that is used by gpadc for Hw
* conversion
* @cal_data array of ADC calibration data structs * @cal_data array of ADC calibration data structs
*/ */
struct ab8500_gpadc { struct ab8500_gpadc {
@ -126,7 +162,8 @@ struct ab8500_gpadc {
struct completion ab8500_gpadc_complete; struct completion ab8500_gpadc_complete;
struct mutex ab8500_gpadc_lock; struct mutex ab8500_gpadc_lock;
struct regulator *regu; struct regulator *regu;
int irq; int irq_sw;
int irq_hw;
struct adc_cal_data cal_data[NBR_CAL_INPUTS]; struct adc_cal_data cal_data[NBR_CAL_INPUTS];
}; };
@ -171,6 +208,7 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel,
gpadc->cal_data[ADC_INPUT_VMAIN].offset) / CALIB_SCALE; gpadc->cal_data[ADC_INPUT_VMAIN].offset) / CALIB_SCALE;
break; break;
case XTAL_TEMP:
case BAT_CTRL: case BAT_CTRL:
case BTEMP_BALL: case BTEMP_BALL:
case ACC_DETECT1: case ACC_DETECT1:
@ -189,6 +227,7 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel,
break; break;
case MAIN_BAT_V: case MAIN_BAT_V:
case VBAT_TRUE_MEAS:
/* For some reason we don't have calibrated data */ /* For some reason we don't have calibrated data */
if (!gpadc->cal_data[ADC_INPUT_VBAT].gain) { if (!gpadc->cal_data[ADC_INPUT_VBAT].gain) {
res = ADC_CH_VBAT_MIN + (ADC_CH_VBAT_MAX - res = ADC_CH_VBAT_MIN + (ADC_CH_VBAT_MAX -
@ -232,6 +271,20 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel,
ADC_RESOLUTION; ADC_RESOLUTION;
break; break;
case IBAT_VIRTUAL_CHANNEL:
/* For some reason we don't have calibrated data */
if (!gpadc->cal_data[ADC_INPUT_IBAT].gain) {
res = ADC_CH_IBAT_MIN + (ADC_CH_IBAT_MAX -
ADC_CH_IBAT_MIN) * ad_value /
ADC_RESOLUTION;
break;
}
/* Here we can use the calibrated data */
res = (int) (ad_value * gpadc->cal_data[ADC_INPUT_IBAT].gain +
gpadc->cal_data[ADC_INPUT_IBAT].offset)
>> CALIB_SHIFT_IBAT;
break;
default: default:
dev_err(gpadc->dev, dev_err(gpadc->dev,
"unknown channel, not possible to convert\n"); "unknown channel, not possible to convert\n");
@ -244,25 +297,35 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel,
EXPORT_SYMBOL(ab8500_gpadc_ad_to_voltage); EXPORT_SYMBOL(ab8500_gpadc_ad_to_voltage);
/** /**
* ab8500_gpadc_convert() - gpadc conversion * ab8500_gpadc_sw_hw_convert() - gpadc conversion
* @channel: analog channel to be converted to digital data * @channel: analog channel to be converted to digital data
* @avg_sample: number of ADC sample to average
* @trig_egde: selected ADC trig edge
* @trig_timer: selected ADC trigger delay timer
* @conv_type: selected conversion type (HW or SW conversion)
* *
* This function converts the selected analog i/p to digital * This function converts the selected analog i/p to digital
* data. * data.
*/ */
int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel) int ab8500_gpadc_sw_hw_convert(struct ab8500_gpadc *gpadc, u8 channel,
u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type)
{ {
int ad_value; int ad_value;
int voltage; int voltage;
ad_value = ab8500_gpadc_read_raw(gpadc, channel); ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample,
if (ad_value < 0) { trig_edge, trig_timer, conv_type);
dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", channel); /* On failure retry a second time */
if (ad_value < 0)
ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample,
trig_edge, trig_timer, conv_type);
if (ad_value < 0) {
dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n",
channel);
return ad_value; return ad_value;
} }
voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value); voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value);
if (voltage < 0) if (voltage < 0)
dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:" dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:"
" %d AD: 0x%x\n", channel, ad_value); " %d AD: 0x%x\n", channel, ad_value);
@ -274,21 +337,46 @@ EXPORT_SYMBOL(ab8500_gpadc_convert);
/** /**
* ab8500_gpadc_read_raw() - gpadc read * ab8500_gpadc_read_raw() - gpadc read
* @channel: analog channel to be read * @channel: analog channel to be read
* @avg_sample: number of ADC sample to average
* @trig_edge: selected trig edge
* @trig_timer: selected ADC trigger delay timer
* @conv_type: selected conversion type (HW or SW conversion)
* *
* This function obtains the raw ADC value, this then needs * This function obtains the raw ADC value for an hardware conversion,
* to be converted by calling ab8500_gpadc_ad_to_voltage() * this then needs to be converted by calling ab8500_gpadc_ad_to_voltage()
*/ */
int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel,
u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type)
{
int raw_data;
raw_data = ab8500_gpadc_double_read_raw(gpadc, channel,
avg_sample, trig_edge, trig_timer, conv_type, NULL);
return raw_data;
}
int ab8500_gpadc_double_read_raw(struct ab8500_gpadc *gpadc, u8 channel,
u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type,
int *ibat)
{ {
int ret; int ret;
int looplimit = 0; int looplimit = 0;
u8 val, low_data, high_data; unsigned long completion_timeout;
u8 val, low_data, high_data, low_data2, high_data2;
u8 val_reg1 = 0;
unsigned int delay_min = 0;
unsigned int delay_max = 0;
u8 data_low_addr, data_high_addr;
if (!gpadc) if (!gpadc)
return -ENODEV; return -ENODEV;
mutex_lock(&gpadc->ab8500_gpadc_lock); /* check if convertion is supported */
if ((gpadc->irq_sw < 0) && (conv_type == ADC_SW))
return -ENOTSUPP;
if ((gpadc->irq_hw < 0) && (conv_type == ADC_HW))
return -ENOTSUPP;
mutex_lock(&gpadc->ab8500_gpadc_lock);
/* Enable VTVout LDO this is required for GPADC */ /* Enable VTVout LDO this is required for GPADC */
pm_runtime_get_sync(gpadc->dev); pm_runtime_get_sync(gpadc->dev);
@ -309,16 +397,34 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
} }
/* Enable GPADC */ /* Enable GPADC */
ret = abx500_mask_and_set_register_interruptible(gpadc->dev, val_reg1 |= EN_GPADC;
AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_GPADC, EN_GPADC);
if (ret < 0) { /* Select the channel source and set average samples */
dev_err(gpadc->dev, "gpadc_conversion: enable gpadc failed\n"); switch (avg_sample) {
goto out; case SAMPLE_1:
val = channel | AVG_1;
break;
case SAMPLE_4:
val = channel | AVG_4;
break;
case SAMPLE_8:
val = channel | AVG_8;
break;
default:
val = channel | AVG_16;
break;
} }
/* Select the channel source and set average samples to 16 */ if (conv_type == ADC_HW) {
ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, ret = abx500_set_register_interruptible(gpadc->dev,
AB8500_GPADC_CTRL2_REG, (channel | SW_AVG_16)); AB8500_GPADC, AB8500_GPADC_CTRL3_REG, val);
val_reg1 |= EN_TRIG_EDGE;
if (trig_edge)
val_reg1 |= EN_FALLING;
}
else
ret = abx500_set_register_interruptible(gpadc->dev,
AB8500_GPADC, AB8500_GPADC_CTRL2_REG, val);
if (ret < 0) { if (ret < 0) {
dev_err(gpadc->dev, dev_err(gpadc->dev,
"gpadc_conversion: set avg samples failed\n"); "gpadc_conversion: set avg samples failed\n");
@ -333,71 +439,129 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
switch (channel) { switch (channel) {
case MAIN_CHARGER_C: case MAIN_CHARGER_C:
case USB_CHARGER_C: case USB_CHARGER_C:
ret = abx500_mask_and_set_register_interruptible(gpadc->dev, val_reg1 |= EN_BUF | EN_ICHAR;
AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
EN_BUF | EN_ICHAR,
EN_BUF | EN_ICHAR);
break; break;
case BTEMP_BALL: case BTEMP_BALL:
if (!is_ab8500_2p0_or_earlier(gpadc->parent)) { if (!is_ab8500_2p0_or_earlier(gpadc->parent)) {
/* Turn on btemp pull-up on ABB 3.0 */ val_reg1 |= EN_BUF | BTEMP_PULL_UP;
ret = abx500_mask_and_set_register_interruptible( /*
gpadc->dev, * Delay might be needed for ABB8500 cut 3.0, if not,
AB8500_GPADC, AB8500_GPADC_CTRL1_REG, * remove when hardware will be availible
EN_BUF | BTEMP_PULL_UP, */
EN_BUF | BTEMP_PULL_UP); delay_min = 1000; /* Delay in micro seconds */
delay_max = 10000; /* large range to optimise sleep mode */
/*
* Delay might be needed for ABB8500 cut 3.0, if not, remove
* when hardware will be available
*/
usleep_range(1000, 1000);
break; break;
} }
/* Intentional fallthrough */ /* Intentional fallthrough */
default: default:
ret = abx500_mask_and_set_register_interruptible(gpadc->dev, val_reg1 |= EN_BUF;
AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF);
break; break;
} }
/* Write configuration to register */
ret = abx500_set_register_interruptible(gpadc->dev,
AB8500_GPADC, AB8500_GPADC_CTRL1_REG, val_reg1);
if (ret < 0) { if (ret < 0) {
dev_err(gpadc->dev, dev_err(gpadc->dev,
"gpadc_conversion: select falling edge failed\n"); "gpadc_conversion: set Control register failed\n");
goto out; goto out;
} }
ret = abx500_mask_and_set_register_interruptible(gpadc->dev, if (delay_min != 0)
AB8500_GPADC, AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV); usleep_range(delay_min, delay_max);
if (ret < 0) {
dev_err(gpadc->dev, if (conv_type == ADC_HW) {
"gpadc_conversion: start s/w conversion failed\n"); /* Set trigger delay timer */
goto out; ret = abx500_set_register_interruptible(gpadc->dev,
AB8500_GPADC, AB8500_GPADC_AUTO_TIMER_REG, trig_timer);
if (ret < 0) {
dev_err(gpadc->dev,
"gpadc_conversion: trig timer failed\n");
goto out;
}
completion_timeout = 2 * HZ;
data_low_addr = AB8500_GPADC_AUTODATAL_REG;
data_high_addr = AB8500_GPADC_AUTODATAH_REG;
} else {
/* Start SW conversion */
ret = abx500_mask_and_set_register_interruptible(gpadc->dev,
AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
ADC_SW_CONV, ADC_SW_CONV);
if (ret < 0) {
dev_err(gpadc->dev,
"gpadc_conversion: start s/w conv failed\n");
goto out;
}
completion_timeout = msecs_to_jiffies(CONVERSION_TIME);
data_low_addr = AB8500_GPADC_MANDATAL_REG;
data_high_addr = AB8500_GPADC_MANDATAH_REG;
} }
/* wait for completion of conversion */ /* wait for completion of conversion */
if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete, if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete,
msecs_to_jiffies(CONVERSION_TIME))) { completion_timeout)) {
dev_err(gpadc->dev, dev_err(gpadc->dev,
"timeout: didn't receive GPADC conversion interrupt\n"); "timeout didn't receive GPADC conv interrupt\n");
ret = -EINVAL; ret = -EINVAL;
goto out; goto out;
} }
/* Read the converted RAW data */ /* Read the converted RAW data */
ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, ret = abx500_get_register_interruptible(gpadc->dev,
AB8500_GPADC_MANDATAL_REG, &low_data); AB8500_GPADC, data_low_addr, &low_data);
if (ret < 0) { if (ret < 0) {
dev_err(gpadc->dev, "gpadc_conversion: read low data failed\n"); dev_err(gpadc->dev, "gpadc_conversion: read low data failed\n");
goto out; goto out;
} }
ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, ret = abx500_get_register_interruptible(gpadc->dev,
AB8500_GPADC_MANDATAH_REG, &high_data); AB8500_GPADC, data_high_addr, &high_data);
if (ret < 0) { if (ret < 0) {
dev_err(gpadc->dev, dev_err(gpadc->dev, "gpadc_conversion: read high data failed\n");
"gpadc_conversion: read high data failed\n");
goto out; goto out;
} }
/* Check if double convertion is required */
if ((channel == BAT_CTRL_AND_IBAT) ||
(channel == VBAT_MEAS_AND_IBAT) ||
(channel == VBAT_TRUE_MEAS_AND_IBAT) ||
(channel == BAT_TEMP_AND_IBAT)) {
if (conv_type == ADC_HW) {
/* not supported */
ret = -ENOTSUPP;
dev_err(gpadc->dev,
"gpadc_conversion: only SW double conversion supported\n");
goto out;
} else {
/* Read the converted RAW data 2 */
ret = abx500_get_register_interruptible(gpadc->dev,
AB8500_GPADC, AB8540_GPADC_MANDATA2L_REG,
&low_data2);
if (ret < 0) {
dev_err(gpadc->dev,
"gpadc_conversion: read sw low data 2 failed\n");
goto out;
}
ret = abx500_get_register_interruptible(gpadc->dev,
AB8500_GPADC, AB8540_GPADC_MANDATA2H_REG,
&high_data2);
if (ret < 0) {
dev_err(gpadc->dev,
"gpadc_conversion: read sw high data 2 failed\n");
goto out;
}
if (ibat != NULL) {
*ibat = (high_data2 << 8) | low_data2;
} else {
dev_warn(gpadc->dev,
"gpadc_conversion: ibat not stored\n");
}
}
}
/* Disable GPADC */ /* Disable GPADC */
ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC,
AB8500_GPADC_CTRL1_REG, DIS_GPADC); AB8500_GPADC_CTRL1_REG, DIS_GPADC);
@ -406,6 +570,7 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
goto out; goto out;
} }
/* Disable VTVout LDO this is required for GPADC */
pm_runtime_mark_last_busy(gpadc->dev); pm_runtime_mark_last_busy(gpadc->dev);
pm_runtime_put_autosuspend(gpadc->dev); pm_runtime_put_autosuspend(gpadc->dev);
@ -422,9 +587,7 @@ out:
*/ */
(void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, (void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC,
AB8500_GPADC_CTRL1_REG, DIS_GPADC); AB8500_GPADC_CTRL1_REG, DIS_GPADC);
pm_runtime_put(gpadc->dev); pm_runtime_put(gpadc->dev);
mutex_unlock(&gpadc->ab8500_gpadc_lock); mutex_unlock(&gpadc->ab8500_gpadc_lock);
dev_err(gpadc->dev, dev_err(gpadc->dev,
"gpadc_conversion: Failed to AD convert channel %d\n", channel); "gpadc_conversion: Failed to AD convert channel %d\n", channel);
@ -433,16 +596,16 @@ out:
EXPORT_SYMBOL(ab8500_gpadc_read_raw); EXPORT_SYMBOL(ab8500_gpadc_read_raw);
/** /**
* ab8500_bm_gpswadcconvend_handler() - isr for s/w gpadc conversion completion * ab8500_bm_gpadcconvend_handler() - isr for gpadc conversion completion
* @irq: irq number * @irq: irq number
* @data: pointer to the data passed during request irq * @data: pointer to the data passed during request irq
* *
* This is a interrupt service routine for s/w gpadc conversion completion. * This is a interrupt service routine for gpadc conversion completion.
* Notifies the gpadc completion is completed and the converted raw value * Notifies the gpadc completion is completed and the converted raw value
* can be read from the registers. * can be read from the registers.
* Returns IRQ status(IRQ_HANDLED) * Returns IRQ status(IRQ_HANDLED)
*/ */
static irqreturn_t ab8500_bm_gpswadcconvend_handler(int irq, void *_gpadc) static irqreturn_t ab8500_bm_gpadcconvend_handler(int irq, void *_gpadc)
{ {
struct ab8500_gpadc *gpadc = _gpadc; struct ab8500_gpadc *gpadc = _gpadc;
@ -461,15 +624,27 @@ static int otp_cal_regs[] = {
AB8500_GPADC_CAL_7, AB8500_GPADC_CAL_7,
}; };
static int otp4_cal_regs[] = {
AB8540_GPADC_OTP4_REG_7,
AB8540_GPADC_OTP4_REG_6,
AB8540_GPADC_OTP4_REG_5,
};
static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc)
{ {
int i; int i;
int ret[ARRAY_SIZE(otp_cal_regs)]; int ret[ARRAY_SIZE(otp_cal_regs)];
u8 gpadc_cal[ARRAY_SIZE(otp_cal_regs)]; u8 gpadc_cal[ARRAY_SIZE(otp_cal_regs)];
int ret_otp4[ARRAY_SIZE(otp4_cal_regs)];
u8 gpadc_otp4[ARRAY_SIZE(otp4_cal_regs)];
int vmain_high, vmain_low; int vmain_high, vmain_low;
int btemp_high, btemp_low; int btemp_high, btemp_low;
int vbat_high, vbat_low; int vbat_high, vbat_low;
int ibat_high, ibat_low;
s64 V_gain, V_offset, V2A_gain, V2A_offset;
struct ab8500 *ab8500;
ab8500 = gpadc->parent;
/* First we read all OTP registers and store the error code */ /* First we read all OTP registers and store the error code */
for (i = 0; i < ARRAY_SIZE(otp_cal_regs); i++) { for (i = 0; i < ARRAY_SIZE(otp_cal_regs); i++) {
@ -489,7 +664,7 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc)
* bt_h/l = btemp_high/low * bt_h/l = btemp_high/low
* vb_h/l = vbat_high/low * vb_h/l = vbat_high/low
* *
* Data bits: * Data bits 8500/9540:
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* |.......|.......|.......|.......|.......|.......|.......|....... * |.......|.......|.......|.......|.......|.......|.......|.......
* | | vm_h9 | vm_h8 * | | vm_h9 | vm_h8
@ -507,6 +682,35 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc)
* | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 |
* |.......|.......|.......|.......|.......|.......|.......|....... * |.......|.......|.......|.......|.......|.......|.......|.......
* *
* Data bits 8540:
* OTP2
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* |.......|.......|.......|.......|.......|.......|.......|.......
* |
* |.......|.......|.......|.......|.......|.......|.......|.......
* | vm_h9 | vm_h8 | vm_h7 | vm_h6 | vm_h5 | vm_h4 | vm_h3 | vm_h2
* |.......|.......|.......|.......|.......|.......|.......|.......
* | vm_h1 | vm_h0 | vm_l4 | vm_l3 | vm_l2 | vm_l1 | vm_l0 | bt_h9
* |.......|.......|.......|.......|.......|.......|.......|.......
* | bt_h8 | bt_h7 | bt_h6 | bt_h5 | bt_h4 | bt_h3 | bt_h2 | bt_h1
* |.......|.......|.......|.......|.......|.......|.......|.......
* | bt_h0 | bt_l4 | bt_l3 | bt_l2 | bt_l1 | bt_l0 | vb_h9 | vb_h8
* |.......|.......|.......|.......|.......|.......|.......|.......
* | vb_h7 | vb_h6 | vb_h5 | vb_h4 | vb_h3 | vb_h2 | vb_h1 | vb_h0
* |.......|.......|.......|.......|.......|.......|.......|.......
* | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 |
* |.......|.......|.......|.......|.......|.......|.......|.......
*
* Data bits 8540:
* OTP4
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
* |.......|.......|.......|.......|.......|.......|.......|.......
* | | ib_h9 | ib_h8 | ib_h7
* |.......|.......|.......|.......|.......|.......|.......|.......
* | ib_h6 | ib_h5 | ib_h4 | ib_h3 | ib_h2 | ib_h1 | ib_h0 | ib_l5
* |.......|.......|.......|.......|.......|.......|.......|.......
* | ib_l4 | ib_l3 | ib_l2 | ib_l1 | ib_l0 |
*
* *
* Ideal output ADC codes corresponding to injected input voltages * Ideal output ADC codes corresponding to injected input voltages
* during manufacturing is: * during manufacturing is:
@ -519,38 +723,116 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc)
* vbat_low: Vin = 2380mV / ADC ideal code = 33 * vbat_low: Vin = 2380mV / ADC ideal code = 33
*/ */
/* Calculate gain and offset for VMAIN if all reads succeeded */ if (is_ab8540(ab8500)) {
if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) { /* Calculate gain and offset for VMAIN if all reads succeeded*/
vmain_high = (((gpadc_cal[0] & 0x03) << 8) | if (!(ret[1] < 0 || ret[2] < 0)) {
((gpadc_cal[1] & 0x3F) << 2) | vmain_high = (((gpadc_cal[1] & 0xFF) << 2) |
((gpadc_cal[2] & 0xC0) >> 6)); ((gpadc_cal[2] & 0xC0) >> 6));
vmain_low = ((gpadc_cal[2] & 0x3E) >> 1);
vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi =
(u16)vmain_high;
gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo =
(u16)vmain_low;
gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE *
(19500 - 315) / (vmain_high - vmain_low); (19500 - 315) / (vmain_high - vmain_low);
gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE *
gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * 19500 - 19500 - (CALIB_SCALE * (19500 - 315) /
(CALIB_SCALE * (19500 - 315) / (vmain_high - vmain_low)) * vmain_high;
(vmain_high - vmain_low)) * vmain_high; } else {
} else {
gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0;
}
/* Read IBAT calibration Data */
for (i = 0; i < ARRAY_SIZE(otp4_cal_regs); i++) {
ret_otp4[i] = abx500_get_register_interruptible(
gpadc->dev, AB8500_OTP_EMUL,
otp4_cal_regs[i], &gpadc_otp4[i]);
if (ret_otp4[i] < 0)
dev_err(gpadc->dev,
"%s: read otp4 reg 0x%02x failed\n",
__func__, otp4_cal_regs[i]);
}
/* Calculate gain and offset for IBAT if all reads succeeded */
if (!(ret_otp4[0] < 0 || ret_otp4[1] < 0 || ret_otp4[2] < 0)) {
ibat_high = (((gpadc_otp4[0] & 0x07) << 7) |
((gpadc_otp4[1] & 0xFE) >> 1));
ibat_low = (((gpadc_otp4[1] & 0x01) << 5) |
((gpadc_otp4[2] & 0xF8) >> 3));
gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi =
(u16)ibat_high;
gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo =
(u16)ibat_low;
V_gain = ((IBAT_VDROP_H - IBAT_VDROP_L)
<< CALIB_SHIFT_IBAT) / (ibat_high - ibat_low);
V_offset = (IBAT_VDROP_H << CALIB_SHIFT_IBAT) -
(((IBAT_VDROP_H - IBAT_VDROP_L) <<
CALIB_SHIFT_IBAT) / (ibat_high - ibat_low))
* ibat_high;
/*
* Result obtained is in mV (at a scale factor),
* we need to calculate gain and offset to get mA
*/
V2A_gain = (ADC_CH_IBAT_MAX - ADC_CH_IBAT_MIN)/
(ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V);
V2A_offset = ((ADC_CH_IBAT_MAX_V * ADC_CH_IBAT_MIN -
ADC_CH_IBAT_MAX * ADC_CH_IBAT_MIN_V)
<< CALIB_SHIFT_IBAT)
/ (ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V);
gpadc->cal_data[ADC_INPUT_IBAT].gain = V_gain * V2A_gain;
gpadc->cal_data[ADC_INPUT_IBAT].offset = V_offset *
V2A_gain + V2A_offset;
} else {
gpadc->cal_data[ADC_INPUT_IBAT].gain = 0;
}
dev_dbg(gpadc->dev, "IBAT gain %llu offset %llu\n",
gpadc->cal_data[ADC_INPUT_IBAT].gain,
gpadc->cal_data[ADC_INPUT_IBAT].offset);
} else {
/* Calculate gain and offset for VMAIN if all reads succeeded */
if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) {
vmain_high = (((gpadc_cal[0] & 0x03) << 8) |
((gpadc_cal[1] & 0x3F) << 2) |
((gpadc_cal[2] & 0xC0) >> 6));
vmain_low = ((gpadc_cal[2] & 0x3E) >> 1);
gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi =
(u16)vmain_high;
gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo =
(u16)vmain_low;
gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE *
(19500 - 315) / (vmain_high - vmain_low);
gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE *
19500 - (CALIB_SCALE * (19500 - 315) /
(vmain_high - vmain_low)) * vmain_high;
} else {
gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0;
}
} }
/* Calculate gain and offset for BTEMP if all reads succeeded */ /* Calculate gain and offset for BTEMP if all reads succeeded */
if (!(ret[2] < 0 || ret[3] < 0 || ret[4] < 0)) { if (!(ret[2] < 0 || ret[3] < 0 || ret[4] < 0)) {
btemp_high = (((gpadc_cal[2] & 0x01) << 9) | btemp_high = (((gpadc_cal[2] & 0x01) << 9) |
(gpadc_cal[3] << 1) | (gpadc_cal[3] << 1) | ((gpadc_cal[4] & 0x80) >> 7));
((gpadc_cal[4] & 0x80) >> 7));
btemp_low = ((gpadc_cal[4] & 0x7C) >> 2); btemp_low = ((gpadc_cal[4] & 0x7C) >> 2);
gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi = (u16)btemp_high;
gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo = (u16)btemp_low;
gpadc->cal_data[ADC_INPUT_BTEMP].gain = gpadc->cal_data[ADC_INPUT_BTEMP].gain =
CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low); CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low);
gpadc->cal_data[ADC_INPUT_BTEMP].offset = CALIB_SCALE * 1300 - gpadc->cal_data[ADC_INPUT_BTEMP].offset = CALIB_SCALE * 1300 -
(CALIB_SCALE * (1300 - 21) / (CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low))
(btemp_high - btemp_low)) * btemp_high; * btemp_high;
} else { } else {
gpadc->cal_data[ADC_INPUT_BTEMP].gain = 0; gpadc->cal_data[ADC_INPUT_BTEMP].gain = 0;
} }
@ -560,9 +842,11 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc)
vbat_high = (((gpadc_cal[4] & 0x03) << 8) | gpadc_cal[5]); vbat_high = (((gpadc_cal[4] & 0x03) << 8) | gpadc_cal[5]);
vbat_low = ((gpadc_cal[6] & 0xFC) >> 2); vbat_low = ((gpadc_cal[6] & 0xFC) >> 2);
gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi = (u16)vbat_high;
gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo = (u16)vbat_low;
gpadc->cal_data[ADC_INPUT_VBAT].gain = CALIB_SCALE * gpadc->cal_data[ADC_INPUT_VBAT].gain = CALIB_SCALE *
(4700 - 2380) / (vbat_high - vbat_low); (4700 - 2380) / (vbat_high - vbat_low);
gpadc->cal_data[ADC_INPUT_VBAT].offset = CALIB_SCALE * 4700 - gpadc->cal_data[ADC_INPUT_VBAT].offset = CALIB_SCALE * 4700 -
(CALIB_SCALE * (4700 - 2380) / (CALIB_SCALE * (4700 - 2380) /
(vbat_high - vbat_low)) * vbat_high; (vbat_high - vbat_low)) * vbat_high;
@ -608,6 +892,31 @@ static int ab8500_gpadc_runtime_idle(struct device *dev)
return 0; return 0;
} }
static int ab8500_gpadc_suspend(struct device *dev)
{
struct ab8500_gpadc *gpadc = dev_get_drvdata(dev);
mutex_lock(&gpadc->ab8500_gpadc_lock);
pm_runtime_get_sync(dev);
regulator_disable(gpadc->regu);
return 0;
}
static int ab8500_gpadc_resume(struct device *dev)
{
struct ab8500_gpadc *gpadc = dev_get_drvdata(dev);
regulator_enable(gpadc->regu);
pm_runtime_mark_last_busy(gpadc->dev);
pm_runtime_put_autosuspend(gpadc->dev);
mutex_unlock(&gpadc->ab8500_gpadc_lock);
return 0;
}
static int ab8500_gpadc_probe(struct platform_device *pdev) static int ab8500_gpadc_probe(struct platform_device *pdev)
{ {
int ret = 0; int ret = 0;
@ -619,13 +928,13 @@ static int ab8500_gpadc_probe(struct platform_device *pdev)
return -ENOMEM; return -ENOMEM;
} }
gpadc->irq = platform_get_irq_byname(pdev, "SW_CONV_END"); gpadc->irq_sw = platform_get_irq_byname(pdev, "SW_CONV_END");
if (gpadc->irq < 0) { if (gpadc->irq_sw < 0)
dev_err(&pdev->dev, "failed to get platform irq-%d\n", dev_err(gpadc->dev, "failed to get platform sw_conv_end irq\n");
gpadc->irq);
ret = gpadc->irq; gpadc->irq_hw = platform_get_irq_byname(pdev, "HW_CONV_END");
goto fail; if (gpadc->irq_hw < 0)
} dev_err(gpadc->dev, "failed to get platform hw_conv_end irq\n");
gpadc->dev = &pdev->dev; gpadc->dev = &pdev->dev;
gpadc->parent = dev_get_drvdata(pdev->dev.parent); gpadc->parent = dev_get_drvdata(pdev->dev.parent);
@ -634,15 +943,31 @@ static int ab8500_gpadc_probe(struct platform_device *pdev)
/* Initialize completion used to notify completion of conversion */ /* Initialize completion used to notify completion of conversion */
init_completion(&gpadc->ab8500_gpadc_complete); init_completion(&gpadc->ab8500_gpadc_complete);
/* Register interrupt - SwAdcComplete */ /* Register interrupts */
ret = request_threaded_irq(gpadc->irq, NULL, if (gpadc->irq_sw >= 0) {
ab8500_bm_gpswadcconvend_handler, ret = request_threaded_irq(gpadc->irq_sw, NULL,
IRQF_ONESHOT | IRQF_NO_SUSPEND | IRQF_SHARED, ab8500_bm_gpadcconvend_handler,
"ab8500-gpadc", gpadc); IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-sw",
if (ret < 0) { gpadc);
dev_err(gpadc->dev, "Failed to register interrupt, irq: %d\n", if (ret < 0) {
gpadc->irq); dev_err(gpadc->dev,
goto fail; "Failed to register interrupt irq: %d\n",
gpadc->irq_sw);
goto fail;
}
}
if (gpadc->irq_hw >= 0) {
ret = request_threaded_irq(gpadc->irq_hw, NULL,
ab8500_bm_gpadcconvend_handler,
IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-hw",
gpadc);
if (ret < 0) {
dev_err(gpadc->dev,
"Failed to register interrupt irq: %d\n",
gpadc->irq_hw);
goto fail_irq;
}
} }
/* VTVout LDO used to power up ab8500-GPADC */ /* VTVout LDO used to power up ab8500-GPADC */
@ -669,11 +994,13 @@ static int ab8500_gpadc_probe(struct platform_device *pdev)
ab8500_gpadc_read_calibration_data(gpadc); ab8500_gpadc_read_calibration_data(gpadc);
list_add_tail(&gpadc->node, &ab8500_gpadc_list); list_add_tail(&gpadc->node, &ab8500_gpadc_list);
dev_dbg(gpadc->dev, "probe success\n"); dev_dbg(gpadc->dev, "probe success\n");
return 0; return 0;
fail_enable: fail_enable:
fail_irq: fail_irq:
free_irq(gpadc->irq, gpadc); free_irq(gpadc->irq_sw, gpadc);
free_irq(gpadc->irq_hw, gpadc);
fail: fail:
kfree(gpadc); kfree(gpadc);
gpadc = NULL; gpadc = NULL;
@ -687,7 +1014,10 @@ static int ab8500_gpadc_remove(struct platform_device *pdev)
/* remove this gpadc entry from the list */ /* remove this gpadc entry from the list */
list_del(&gpadc->node); list_del(&gpadc->node);
/* remove interrupt - completion of Sw ADC conversion */ /* remove interrupt - completion of Sw ADC conversion */
free_irq(gpadc->irq, gpadc); if (gpadc->irq_sw >= 0)
free_irq(gpadc->irq_sw, gpadc);
if (gpadc->irq_hw >= 0)
free_irq(gpadc->irq_hw, gpadc);
pm_runtime_get_sync(gpadc->dev); pm_runtime_get_sync(gpadc->dev);
pm_runtime_disable(gpadc->dev); pm_runtime_disable(gpadc->dev);
@ -707,6 +1037,9 @@ static const struct dev_pm_ops ab8500_gpadc_pm_ops = {
SET_RUNTIME_PM_OPS(ab8500_gpadc_runtime_suspend, SET_RUNTIME_PM_OPS(ab8500_gpadc_runtime_suspend,
ab8500_gpadc_runtime_resume, ab8500_gpadc_runtime_resume,
ab8500_gpadc_runtime_idle) ab8500_gpadc_runtime_idle)
SET_SYSTEM_SLEEP_PM_OPS(ab8500_gpadc_suspend,
ab8500_gpadc_resume)
}; };
static struct platform_driver ab8500_gpadc_driver = { static struct platform_driver ab8500_gpadc_driver = {
@ -729,10 +1062,30 @@ static void __exit ab8500_gpadc_exit(void)
platform_driver_unregister(&ab8500_gpadc_driver); platform_driver_unregister(&ab8500_gpadc_driver);
} }
/**
* ab8540_gpadc_get_otp() - returns OTP values
*
*/
void ab8540_gpadc_get_otp(struct ab8500_gpadc *gpadc,
u16 *vmain_l, u16 *vmain_h, u16 *btemp_l, u16 *btemp_h,
u16 *vbat_l, u16 *vbat_h, u16 *ibat_l, u16 *ibat_h)
{
*vmain_l = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo;
*vmain_h = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi;
*btemp_l = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo;
*btemp_h = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi;
*vbat_l = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo;
*vbat_h = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi;
*ibat_l = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo;
*ibat_h = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi;
return ;
}
subsys_initcall_sync(ab8500_gpadc_init); subsys_initcall_sync(ab8500_gpadc_init);
module_exit(ab8500_gpadc_exit); module_exit(ab8500_gpadc_exit);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson"); MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson,"
"M'boumba Cedric Madianga");
MODULE_ALIAS("platform:ab8500_gpadc"); MODULE_ALIAS("platform:ab8500_gpadc");
MODULE_DESCRIPTION("AB8500 GPADC driver"); MODULE_DESCRIPTION("AB8500 GPADC driver");

View File

@ -15,19 +15,30 @@
#include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500/ab8500.h>
#include <linux/mfd/abx500/ab8500-sysctrl.h> #include <linux/mfd/abx500/ab8500-sysctrl.h>
/* RtcCtrl bits */
#define AB8500_ALARM_MIN_LOW 0x08
#define AB8500_ALARM_MIN_MID 0x09
#define RTC_CTRL 0x0B
#define RTC_ALARM_ENABLE 0x4
static struct device *sysctrl_dev; static struct device *sysctrl_dev;
void ab8500_power_off(void) void ab8500_power_off(void)
{ {
sigset_t old; sigset_t old;
sigset_t all; sigset_t all;
static char *pss[] = {"ab8500_ac", "ab8500_usb"}; static char *pss[] = {"ab8500_ac", "pm2301", "ab8500_usb"};
int i; int i;
bool charger_present = false; bool charger_present = false;
union power_supply_propval val; union power_supply_propval val;
struct power_supply *psy; struct power_supply *psy;
int ret; int ret;
if (sysctrl_dev == NULL) {
pr_err("%s: sysctrl not initialized\n", __func__);
return;
}
/* /*
* If we have a charger connected and we're powering off, * If we have a charger connected and we're powering off,
* reboot into charge-only mode. * reboot into charge-only mode.
@ -74,6 +85,63 @@ shutdown:
} }
} }
/*
* Use the AB WD to reset the platform. It will perform a hard
* reset instead of a soft reset. Write the reset reason to
* the AB before reset, which can be read upon restart.
*/
void ab8500_restart(char mode, const char *cmd)
{
struct ab8500_platform_data *plat;
struct ab8500_sysctrl_platform_data *pdata;
u16 reason = 0;
u8 val;
if (sysctrl_dev == NULL) {
pr_err("%s: sysctrl not initialized\n", __func__);
return;
}
plat = dev_get_platdata(sysctrl_dev->parent);
pdata = plat->sysctrl;
if (pdata->reboot_reason_code)
reason = pdata->reboot_reason_code(cmd);
else
pr_warn("[%s] No reboot reason set. Default reason %d\n",
__func__, reason);
/*
* Disable RTC alarm, just a precaution so that no alarm
* is running when WD reset is executed.
*/
abx500_get_register_interruptible(sysctrl_dev, AB8500_RTC,
RTC_CTRL , &val);
abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC,
RTC_CTRL , (val & ~RTC_ALARM_ENABLE));
/*
* Android is not using the RTC alarm registers during reboot
* so we borrow them for writing the reason of reset
*/
/* reason[8 LSB] */
val = reason & 0xFF;
abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC,
AB8500_ALARM_MIN_LOW , val);
/* reason[8 MSB] */
val = (reason>>8) & 0xFF;
abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC,
AB8500_ALARM_MIN_MID , val);
/* Setting WD timeout to 0 */
ab8500_sysctrl_write(AB8500_MAINWDOGTIMER, 0xFF, 0x0);
/* Setting the parameters to AB8500 WD*/
ab8500_sysctrl_write(AB8500_MAINWDOGCTRL, 0xFF, (AB8500_ENABLE_WD |
AB8500_WD_RESTART_ON_EXPIRE | AB8500_KICK_WD));
}
static inline bool valid_bank(u8 bank) static inline bool valid_bank(u8 bank)
{ {
return ((bank == AB8500_SYS_CTRL1_BLOCK) || return ((bank == AB8500_SYS_CTRL1_BLOCK) ||
@ -85,7 +153,7 @@ int ab8500_sysctrl_read(u16 reg, u8 *value)
u8 bank; u8 bank;
if (sysctrl_dev == NULL) if (sysctrl_dev == NULL)
return -EAGAIN; return -EINVAL;
bank = (reg >> 8); bank = (reg >> 8);
if (!valid_bank(bank)) if (!valid_bank(bank))
@ -101,7 +169,7 @@ int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value)
u8 bank; u8 bank;
if (sysctrl_dev == NULL) if (sysctrl_dev == NULL)
return -EAGAIN; return -EINVAL;
bank = (reg >> 8); bank = (reg >> 8);
if (!valid_bank(bank)) if (!valid_bank(bank))
@ -114,28 +182,36 @@ EXPORT_SYMBOL(ab8500_sysctrl_write);
static int ab8500_sysctrl_probe(struct platform_device *pdev) static int ab8500_sysctrl_probe(struct platform_device *pdev)
{ {
struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent);
struct ab8500_platform_data *plat; struct ab8500_platform_data *plat;
struct ab8500_sysctrl_platform_data *pdata; struct ab8500_sysctrl_platform_data *pdata;
sysctrl_dev = &pdev->dev;
plat = dev_get_platdata(pdev->dev.parent); plat = dev_get_platdata(pdev->dev.parent);
if (!(plat && plat->sysctrl))
return -EINVAL;
if (plat->pm_power_off) if (plat->pm_power_off)
pm_power_off = ab8500_power_off; pm_power_off = ab8500_power_off;
pdata = plat->sysctrl; pdata = plat->sysctrl;
if (pdata) { if (pdata) {
int ret, i, j; int last, ret, i, j;
for (i = AB8500_SYSCLKREQ1RFCLKBUF; if (is_ab8505(ab8500))
i <= AB8500_SYSCLKREQ8RFCLKBUF; i++) { last = AB8500_SYSCLKREQ4RFCLKBUF;
else
last = AB8500_SYSCLKREQ8RFCLKBUF;
for (i = AB8500_SYSCLKREQ1RFCLKBUF; i <= last; i++) {
j = i - AB8500_SYSCLKREQ1RFCLKBUF; j = i - AB8500_SYSCLKREQ1RFCLKBUF;
ret = ab8500_sysctrl_write(i, 0xff, ret = ab8500_sysctrl_write(i, 0xff,
pdata->initial_req_buf_config[j]); pdata->initial_req_buf_config[j]);
dev_dbg(&pdev->dev, dev_dbg(&pdev->dev,
"Setting SysClkReq%dRfClkBuf 0x%X\n", "Setting SysClkReq%dRfClkBuf 0x%X\n",
j + 1, j + 1,
pdata->initial_req_buf_config[j]); pdata->initial_req_buf_config[j]);
if (ret < 0) { if (ret < 0) {
dev_err(&pdev->dev, dev_err(&pdev->dev,
"unable to set sysClkReq%dRfClkBuf: " "unable to set sysClkReq%dRfClkBuf: "

View File

@ -353,13 +353,6 @@ config BATTERY_GOLDFISH
Say Y to enable support for the battery and AC power in the Say Y to enable support for the battery and AC power in the
Goldfish emulator. Goldfish emulator.
config CHARGER_PM2301
bool "PM2301 Battery Charger Driver"
depends on AB8500_BM
help
Say Y to include support for PM2301 charger driver.
Depends on AB8500 battery management core.
source "drivers/power/reset/Kconfig" source "drivers/power/reset/Kconfig"
endif # POWER_SUPPLY endif # POWER_SUPPLY

View File

@ -39,7 +39,7 @@ obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o
obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
@ -47,7 +47,6 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_PM2301) += pm2301_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o

View File

@ -407,15 +407,27 @@ static const struct abx500_fg_parameters fg = {
.battok_raising_th_sel1 = 2860, .battok_raising_th_sel1 = 2860,
.maint_thres = 95, .maint_thres = 95,
.user_cap_limit = 15, .user_cap_limit = 15,
.pcut_enable = 1,
.pcut_max_time = 127,
.pcut_flag_time = 112,
.pcut_max_restart = 15,
.pcut_debounce_time = 2,
}; };
static const struct abx500_maxim_parameters maxi_params = { static const struct abx500_maxim_parameters ab8500_maxi_params = {
.ena_maxi = true, .ena_maxi = true,
.chg_curr = 910, .chg_curr = 910,
.wait_cycles = 10, .wait_cycles = 10,
.charger_curr_step = 100, .charger_curr_step = 100,
}; };
static const struct abx500_maxim_parameters abx540_maxi_params = {
.ena_maxi = true,
.chg_curr = 3000,
.wait_cycles = 10,
.charger_curr_step = 200,
};
static const struct abx500_bm_charger_parameters chg = { static const struct abx500_bm_charger_parameters chg = {
.usb_volt_max = 5500, .usb_volt_max = 5500,
.usb_curr_max = 1500, .usb_curr_max = 1500,
@ -423,6 +435,46 @@ static const struct abx500_bm_charger_parameters chg = {
.ac_curr_max = 1500, .ac_curr_max = 1500,
}; };
/*
* This array maps the raw hex value to charger output current used by the
* AB8500 values
*/
static int ab8500_charge_output_curr_map[] = {
100, 200, 300, 400, 500, 600, 700, 800,
900, 1000, 1100, 1200, 1300, 1400, 1500, 1500,
};
static int ab8540_charge_output_curr_map[] = {
0, 0, 0, 75, 100, 125, 150, 175,
200, 225, 250, 275, 300, 325, 350, 375,
400, 425, 450, 475, 500, 525, 550, 575,
600, 625, 650, 675, 700, 725, 750, 775,
800, 825, 850, 875, 900, 925, 950, 975,
1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175,
1200, 1225, 1250, 1275, 1300, 1325, 1350, 1375,
1400, 1425, 1450, 1500, 1600, 1700, 1900, 2000,
};
/*
* This array maps the raw hex value to charger input current used by the
* AB8500 values
*/
static int ab8500_charge_input_curr_map[] = {
50, 98, 193, 290, 380, 450, 500, 600,
700, 800, 900, 1000, 1100, 1300, 1400, 1500,
};
static int ab8540_charge_input_curr_map[] = {
25, 50, 75, 100, 125, 150, 175, 200,
225, 250, 275, 300, 325, 350, 375, 400,
425, 450, 475, 500, 525, 550, 575, 600,
625, 650, 675, 700, 725, 750, 775, 800,
825, 850, 875, 900, 925, 950, 975, 1000,
1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200,
1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400,
1425, 1450, 1475, 1500, 1500, 1500, 1500, 1500,
};
struct abx500_bm_data ab8500_bm_data = { struct abx500_bm_data ab8500_bm_data = {
.temp_under = 3, .temp_under = 3,
.temp_low = 8, .temp_low = 8,
@ -442,15 +494,53 @@ struct abx500_bm_data ab8500_bm_data = {
.fg_res = 100, .fg_res = 100,
.cap_levels = &cap_levels, .cap_levels = &cap_levels,
.bat_type = bat_type_thermistor, .bat_type = bat_type_thermistor,
.n_btypes = 3, .n_btypes = ARRAY_SIZE(bat_type_thermistor),
.batt_id = 0, .batt_id = 0,
.interval_charging = 5, .interval_charging = 5,
.interval_not_charging = 120, .interval_not_charging = 120,
.temp_hysteresis = 3, .temp_hysteresis = 3,
.gnd_lift_resistance = 34, .gnd_lift_resistance = 34,
.maxi = &maxi_params, .chg_output_curr = ab8500_charge_output_curr_map,
.n_chg_out_curr = ARRAY_SIZE(ab8500_charge_output_curr_map),
.maxi = &ab8500_maxi_params,
.chg_params = &chg, .chg_params = &chg,
.fg_params = &fg, .fg_params = &fg,
.chg_input_curr = ab8500_charge_input_curr_map,
.n_chg_in_curr = ARRAY_SIZE(ab8500_charge_input_curr_map),
};
struct abx500_bm_data ab8540_bm_data = {
.temp_under = 3,
.temp_low = 8,
.temp_high = 43,
.temp_over = 48,
.main_safety_tmr_h = 4,
.temp_interval_chg = 20,
.temp_interval_nochg = 120,
.usb_safety_tmr_h = 4,
.bkup_bat_v = BUP_VCH_SEL_2P6V,
.bkup_bat_i = BUP_ICH_SEL_150UA,
.no_maintenance = false,
.capacity_scaling = false,
.adc_therm = ABx500_ADC_THERM_BATCTRL,
.chg_unknown_bat = false,
.enable_overshoot = false,
.fg_res = 100,
.cap_levels = &cap_levels,
.bat_type = bat_type_thermistor,
.n_btypes = ARRAY_SIZE(bat_type_thermistor),
.batt_id = 0,
.interval_charging = 5,
.interval_not_charging = 120,
.temp_hysteresis = 3,
.gnd_lift_resistance = 0,
.maxi = &abx540_maxi_params,
.chg_params = &chg,
.fg_params = &fg,
.chg_output_curr = ab8540_charge_output_curr_map,
.n_chg_out_curr = ARRAY_SIZE(ab8540_charge_output_curr_map),
.chg_input_curr = ab8540_charge_input_curr_map,
.n_chg_in_curr = ARRAY_SIZE(ab8540_charge_input_curr_map),
}; };
int ab8500_bm_of_probe(struct device *dev, int ab8500_bm_of_probe(struct device *dev,

View File

@ -42,6 +42,9 @@
#define BTEMP_BATCTRL_CURR_SRC_16UA 16 #define BTEMP_BATCTRL_CURR_SRC_16UA 16
#define BTEMP_BATCTRL_CURR_SRC_18UA 18 #define BTEMP_BATCTRL_CURR_SRC_18UA 18
#define BTEMP_BATCTRL_CURR_SRC_60UA 60
#define BTEMP_BATCTRL_CURR_SRC_120UA 120
#define to_ab8500_btemp_device_info(x) container_of((x), \ #define to_ab8500_btemp_device_info(x) container_of((x), \
struct ab8500_btemp, btemp_psy); struct ab8500_btemp, btemp_psy);
@ -76,8 +79,8 @@ struct ab8500_btemp_ranges {
* @dev: Pointer to the structure device * @dev: Pointer to the structure device
* @node: List of AB8500 BTEMPs, hence prepared for reentrance * @node: List of AB8500 BTEMPs, hence prepared for reentrance
* @curr_source: What current source we use, in uA * @curr_source: What current source we use, in uA
* @bat_temp: Battery temperature in degree Celcius * @bat_temp: Dispatched battery temperature in degree Celcius
* @prev_bat_temp Last dispatched battery temperature * @prev_bat_temp Last measured battery temperature in degree Celcius
* @parent: Pointer to the struct ab8500 * @parent: Pointer to the struct ab8500
* @gpadc: Pointer to the struct gpadc * @gpadc: Pointer to the struct gpadc
* @fg: Pointer to the struct fg * @fg: Pointer to the struct fg
@ -155,7 +158,7 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) { if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) {
/* /*
* If the battery has internal NTC, we use the current * If the battery has internal NTC, we use the current
* source to calculate the resistance, 7uA or 20uA * source to calculate the resistance.
*/ */
rbs = (v_batctrl * 1000 rbs = (v_batctrl * 1000
- di->bm->gnd_lift_resistance * inst_curr) - di->bm->gnd_lift_resistance * inst_curr)
@ -216,7 +219,12 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
/* Only do this for batteries with internal NTC */ /* Only do this for batteries with internal NTC */
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) { if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
if (is_ab9540(di->parent) || is_ab8505(di->parent)) { if (is_ab8540(di->parent)) {
if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_60UA)
curr = BAT_CTRL_60U_ENA;
else
curr = BAT_CTRL_120U_ENA;
} else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA) if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA)
curr = BAT_CTRL_16U_ENA; curr = BAT_CTRL_16U_ENA;
else else
@ -257,7 +265,14 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
} else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) { } else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
dev_dbg(di->dev, "Disable BATCTRL curr source\n"); dev_dbg(di->dev, "Disable BATCTRL curr source\n");
if (is_ab9540(di->parent) || is_ab8505(di->parent)) { if (is_ab8540(di->parent)) {
/* Write 0 to the curr bits */
ret = abx500_mask_and_set_register_interruptible(
di->dev,
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA,
~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA));
} else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
/* Write 0 to the curr bits */ /* Write 0 to the curr bits */
ret = abx500_mask_and_set_register_interruptible( ret = abx500_mask_and_set_register_interruptible(
di->dev, di->dev,
@ -314,7 +329,13 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
* if we got an error above * if we got an error above
*/ */
disable_curr_source: disable_curr_source:
if (is_ab9540(di->parent) || is_ab8505(di->parent)) { if (is_ab8540(di->parent)) {
/* Write 0 to the curr bits */
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA,
~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA));
} else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
/* Write 0 to the curr bits */ /* Write 0 to the curr bits */
ret = abx500_mask_and_set_register_interruptible(di->dev, ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
@ -541,7 +562,9 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
{ {
int res; int res;
u8 i; u8 i;
if (is_ab9540(di->parent) || is_ab8505(di->parent)) if (is_ab8540(di->parent))
di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA;
else if (is_ab9540(di->parent) || is_ab8505(di->parent))
di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
else else
di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
@ -579,12 +602,17 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
/* /*
* We only have to change current source if the * We only have to change current source if the
* detected type is Type 1, else we use the 7uA source * detected type is Type 1.
*/ */
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
di->bm->batt_id == 1) { di->bm->batt_id == 1) {
if (is_ab9540(di->parent) || is_ab8505(di->parent)) { if (is_ab8540(di->parent)) {
dev_dbg(di->dev, "Set BATCTRL current source to 16uA\n"); dev_dbg(di->dev,
"Set BATCTRL current source to 60uA\n");
di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA;
} else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
dev_dbg(di->dev,
"Set BATCTRL current source to 16uA\n");
di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
} else { } else {
dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
@ -604,22 +632,37 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
static void ab8500_btemp_periodic_work(struct work_struct *work) static void ab8500_btemp_periodic_work(struct work_struct *work)
{ {
int interval; int interval;
int bat_temp;
struct ab8500_btemp *di = container_of(work, struct ab8500_btemp *di = container_of(work,
struct ab8500_btemp, btemp_periodic_work.work); struct ab8500_btemp, btemp_periodic_work.work);
if (!di->initialized) { if (!di->initialized) {
di->initialized = true;
/* Identify the battery */ /* Identify the battery */
if (ab8500_btemp_id(di) < 0) if (ab8500_btemp_id(di) < 0)
dev_warn(di->dev, "failed to identify the battery\n"); dev_warn(di->dev, "failed to identify the battery\n");
} }
di->bat_temp = ab8500_btemp_measure_temp(di); bat_temp = ab8500_btemp_measure_temp(di);
/*
if (di->bat_temp != di->prev_bat_temp) { * Filter battery temperature.
di->prev_bat_temp = di->bat_temp; * Allow direct updates on temperature only if two samples result in
* same temperature. Else only allow 1 degree change from previous
* reported value in the direction of the new measurement.
*/
if ((bat_temp == di->prev_bat_temp) || !di->initialized) {
if ((di->bat_temp != di->prev_bat_temp) || !di->initialized) {
di->initialized = true;
di->bat_temp = bat_temp;
power_supply_changed(&di->btemp_psy);
}
} else if (bat_temp < di->prev_bat_temp) {
di->bat_temp--;
power_supply_changed(&di->btemp_psy);
} else if (bat_temp > di->prev_bat_temp) {
di->bat_temp++;
power_supply_changed(&di->btemp_psy); power_supply_changed(&di->btemp_psy);
} }
di->prev_bat_temp = bat_temp;
if (di->events.ac_conn || di->events.usb_conn) if (di->events.ac_conn || di->events.usb_conn)
interval = di->bm->temp_interval_chg; interval = di->bm->temp_interval_chg;

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@
#define MILLI_TO_MICRO 1000 #define MILLI_TO_MICRO 1000
#define FG_LSB_IN_MA 1627 #define FG_LSB_IN_MA 1627
#define QLSB_NANO_AMP_HOURS_X10 1129 #define QLSB_NANO_AMP_HOURS_X10 1071
#define INS_CURR_TIMEOUT (3 * HZ) #define INS_CURR_TIMEOUT (3 * HZ)
#define SEC_TO_SAMPLE(S) (S * 4) #define SEC_TO_SAMPLE(S) (S * 4)
@ -672,11 +672,11 @@ int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
/* /*
* Convert to unit value in mA * Convert to unit value in mA
* Full scale input voltage is * Full scale input voltage is
* 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA * 63.160mV => LSB = 63.160mV/(4096*res) = 1.542mA
* Given a 250ms conversion cycle time the LSB corresponds * Given a 250ms conversion cycle time the LSB corresponds
* to 112.9 nAh. Convert to current by dividing by the conversion * to 107.1 nAh. Convert to current by dividing by the conversion
* time in hours (250ms = 1 / (3600 * 4)h) * time in hours (250ms = 1 / (3600 * 4)h)
* 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm * 107.1nAh assumes 10mOhm, but fg_res is in 0.1mOhm
*/ */
val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) / val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) /
(1000 * di->bm->fg_res); (1000 * di->bm->fg_res);
@ -1354,9 +1354,6 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
* algorithm says. * algorithm says.
*/ */
di->bat_cap.prev_percent = 1; di->bat_cap.prev_percent = 1;
di->bat_cap.permille = 1;
di->bat_cap.prev_mah = 1;
di->bat_cap.mah = 1;
percent = 1; percent = 1;
changed = true; changed = true;
@ -1683,7 +1680,6 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
break; break;
case AB8500_FG_DISCHARGE_WAKEUP: case AB8500_FG_DISCHARGE_WAKEUP:
ab8500_fg_coulomb_counter(di, true);
ab8500_fg_calc_cap_discharge_voltage(di, true); ab8500_fg_calc_cap_discharge_voltage(di, true);
di->fg_samples = SEC_TO_SAMPLE( di->fg_samples = SEC_TO_SAMPLE(
@ -1768,9 +1764,10 @@ static void ab8500_fg_algorithm(struct ab8500_fg *di)
ab8500_fg_algorithm_discharging(di); ab8500_fg_algorithm_discharging(di);
} }
dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d %d "
"%d %d %d %d %d %d %d\n", "%d %d %d %d %d %d %d\n",
di->bat_cap.max_mah_design, di->bat_cap.max_mah_design,
di->bat_cap.max_mah,
di->bat_cap.mah, di->bat_cap.mah,
di->bat_cap.permille, di->bat_cap.permille,
di->bat_cap.level, di->bat_cap.level,
@ -1982,7 +1979,7 @@ static void ab8500_fg_instant_work(struct work_struct *work)
} }
/** /**
* ab8500_fg_cc_data_end_handler() - isr to get battery avg current. * ab8500_fg_cc_data_end_handler() - end of data conversion isr.
* @irq: interrupt number * @irq: interrupt number
* @_di: pointer to the ab8500_fg structure * @_di: pointer to the ab8500_fg structure
* *
@ -2002,7 +1999,7 @@ static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di)
} }
/** /**
* ab8500_fg_cc_convend_handler() - isr to get battery avg current. * ab8500_fg_cc_int_calib_handler () - end of calibration isr.
* @irq: interrupt number * @irq: interrupt number
* @_di: pointer to the ab8500_fg structure * @_di: pointer to the ab8500_fg structure
* *
@ -2153,9 +2150,7 @@ static int ab8500_fg_get_property(struct power_supply *psy,
val->intval = di->bat_cap.prev_mah; val->intval = di->bat_cap.prev_mah;
break; break;
case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_CAPACITY:
if (di->bm->capacity_scaling) if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
val->intval = di->bat_cap.cap_scale.scaled_cap;
else if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
di->flags.batt_id_received) di->flags.batt_id_received)
val->intval = 100; val->intval = 100;
else else
@ -2344,6 +2339,50 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
dev_err(di->dev, "BattOk init write failed.\n"); dev_err(di->dev, "BattOk init write failed.\n");
goto out; goto out;
} }
if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
abx500_get_chip_id(di->dev) >= AB8500_CUT2P0)
|| is_ab8540(di->parent)) {
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_MAX_TIME_REG, di->bm->fg_params->pcut_max_time);
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__);
goto out;
};
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time);
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__);
goto out;
};
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart);
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__);
goto out;
};
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time);
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__);
goto out;
};
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable);
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__);
goto out;
};
}
out: out:
return ret; return ret;
} }
@ -2546,6 +2585,428 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
return ret; return ret;
} }
static ssize_t ab8505_powercut_flagtime_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_FLAG_TIME_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
fail:
return ret;
}
static ssize_t ab8505_powercut_flagtime_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
long unsigned reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
reg_value = simple_strtoul(buf, NULL, 10);
if (reg_value > 0x7F) {
dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n");
goto fail;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value);
if (ret < 0)
dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n");
fail:
return count;
}
static ssize_t ab8505_powercut_maxtime_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_MAX_TIME_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_MAX_TIME_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
fail:
return ret;
}
static ssize_t ab8505_powercut_maxtime_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
int reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
reg_value = simple_strtoul(buf, NULL, 10);
if (reg_value > 0x7F) {
dev_err(dev, "Incorrect parameter, echo 0 (0.0s) - 127 (1.98s) for maxtime\n");
goto fail;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_MAX_TIME_REG, (u8)reg_value);
if (ret < 0)
dev_err(dev, "Failed to set AB8505_RTC_PCUT_MAX_TIME_REG\n");
fail:
return count;
}
static ssize_t ab8505_powercut_restart_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_RESTART_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF));
fail:
return ret;
}
static ssize_t ab8505_powercut_restart_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
int reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
reg_value = simple_strtoul(buf, NULL, 10);
if (reg_value > 0xF) {
dev_err(dev, "Incorrect parameter, echo 0 - 15 for number of restart\n");
goto fail;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_RESTART_REG, (u8)reg_value);
if (ret < 0)
dev_err(dev, "Failed to set AB8505_RTC_PCUT_RESTART_REG\n");
fail:
return count;
}
static ssize_t ab8505_powercut_timer_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_TIME_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_TIME_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
fail:
return ret;
}
static ssize_t ab8505_powercut_restart_counter_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_RESTART_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4);
fail:
return ret;
}
static ssize_t ab8505_powercut_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
if (ret < 0)
goto fail;
return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1));
fail:
return ret;
}
static ssize_t ab8505_powercut_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
int reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
reg_value = simple_strtoul(buf, NULL, 10);
if (reg_value > 0x1) {
dev_err(dev, "Incorrect parameter, echo 0/1 to disable/enable Pcut feature\n");
goto fail;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_CTL_STATUS_REG, (u8)reg_value);
if (ret < 0)
dev_err(dev, "Failed to set AB8505_RTC_PCUT_CTL_STATUS_REG\n");
fail:
return count;
}
static ssize_t ab8505_powercut_flag_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4));
fail:
return ret;
}
static ssize_t ab8505_powercut_debounce_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_DEBOUNCE_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_DEBOUNCE_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7));
fail:
return ret;
}
static ssize_t ab8505_powercut_debounce_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
int reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
reg_value = simple_strtoul(buf, NULL, 10);
if (reg_value > 0x7) {
dev_err(dev, "Incorrect parameter, echo 0 to 7 for debounce setting\n");
goto fail;
}
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_DEBOUNCE_REG, (u8)reg_value);
if (ret < 0)
dev_err(dev, "Failed to set AB8505_RTC_PCUT_DEBOUNCE_REG\n");
fail:
return count;
}
static ssize_t ab8505_powercut_enable_status_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
u8 reg_value;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
if (ret < 0) {
dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
goto fail;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5));
fail:
return ret;
}
static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
__ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write),
__ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write),
__ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_restart_read, ab8505_powercut_restart_write),
__ATTR(powercut_timer, S_IRUGO, ab8505_powercut_timer_read, NULL),
__ATTR(powercut_restart_counter, S_IRUGO,
ab8505_powercut_restart_counter_read, NULL),
__ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_read, ab8505_powercut_write),
__ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL),
__ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP),
ab8505_powercut_debounce_read, ab8505_powercut_debounce_write),
__ATTR(powercut_enable_status, S_IRUGO,
ab8505_powercut_enable_status_read, NULL),
};
static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev)
{
unsigned int i, j;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0)
|| is_ab8540(di->parent)) {
for (j = 0; j < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); j++)
if (device_create_file(dev, &ab8505_fg_sysfs_psy_attrs[j]))
goto sysfs_psy_create_attrs_failed_ab8505;
}
return 0;
sysfs_psy_create_attrs_failed_ab8505:
dev_err(dev, "Failed creating sysfs psy attrs for ab8505.\n");
while (j--)
device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]);
return -EIO;
}
static void ab8500_fg_sysfs_psy_remove_attrs(struct device *dev)
{
unsigned int i;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
di = to_ab8500_fg_device_info(psy);
if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0)
|| is_ab8540(di->parent)) {
for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++)
(void)device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]);
}
}
/* Exposure to the sysfs interface <<END>> */ /* Exposure to the sysfs interface <<END>> */
#if defined(CONFIG_PM) #if defined(CONFIG_PM)
@ -2607,6 +3068,7 @@ static int ab8500_fg_remove(struct platform_device *pdev)
ab8500_fg_sysfs_exit(di); ab8500_fg_sysfs_exit(di);
flush_scheduled_work(); flush_scheduled_work();
ab8500_fg_sysfs_psy_remove_attrs(di->fg_psy.dev);
power_supply_unregister(&di->fg_psy); power_supply_unregister(&di->fg_psy);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
return ret; return ret;
@ -2772,6 +3234,13 @@ static int ab8500_fg_probe(struct platform_device *pdev)
goto free_irq; goto free_irq;
} }
ret = ab8500_fg_sysfs_psy_create_attrs(di->fg_psy.dev);
if (ret) {
dev_err(di->dev, "failed to create FG psy\n");
ab8500_fg_sysfs_exit(di);
goto free_irq;
}
/* Calibrate the fg first time */ /* Calibrate the fg first time */
di->flags.calibrate = true; di->flags.calibrate = true;
di->calib_state = AB8500_FG_CALIB_INIT; di->calib_state = AB8500_FG_CALIB_INIT;

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (C) ST-Ericsson SA 2012 * Copyright (C) ST-Ericsson SA 2012
* Copyright (c) 2012 Sony Mobile Communications AB
* *
* Charging algorithm driver for abx500 variants * Charging algorithm driver for abx500 variants
* *
@ -8,11 +9,13 @@
* Johan Palsson <johan.palsson@stericsson.com> * Johan Palsson <johan.palsson@stericsson.com>
* Karl Komierowski <karl.komierowski@stericsson.com> * Karl Komierowski <karl.komierowski@stericsson.com>
* Arun R Murthy <arun.murthy@stericsson.com> * Arun R Murthy <arun.murthy@stericsson.com>
* Author: Imre Sunyi <imre.sunyi@sonymobile.com>
*/ */
#include <linux/init.h> #include <linux/init.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/hrtimer.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/slab.h> #include <linux/slab.h>
@ -24,8 +27,10 @@
#include <linux/of.h> #include <linux/of.h>
#include <linux/mfd/core.h> #include <linux/mfd/core.h>
#include <linux/mfd/abx500.h> #include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500.h>
#include <linux/mfd/abx500/ux500_chargalg.h> #include <linux/mfd/abx500/ux500_chargalg.h>
#include <linux/mfd/abx500/ab8500-bm.h> #include <linux/mfd/abx500/ab8500-bm.h>
#include <linux/notifier.h>
/* Watchdog kick interval */ /* Watchdog kick interval */
#define CHG_WD_INTERVAL (6 * HZ) #define CHG_WD_INTERVAL (6 * HZ)
@ -33,6 +38,18 @@
/* End-of-charge criteria counter */ /* End-of-charge criteria counter */
#define EOC_COND_CNT 10 #define EOC_COND_CNT 10
/* One hour expressed in seconds */
#define ONE_HOUR_IN_SECONDS 3600
/* Five minutes expressed in seconds */
#define FIVE_MINUTES_IN_SECONDS 300
/* Plus margin for the low battery threshold */
#define BAT_PLUS_MARGIN (100)
#define CHARGALG_CURR_STEP_LOW 0
#define CHARGALG_CURR_STEP_HIGH 100
#define to_abx500_chargalg_device_info(x) container_of((x), \ #define to_abx500_chargalg_device_info(x) container_of((x), \
struct abx500_chargalg, chargalg_psy); struct abx500_chargalg, chargalg_psy);
@ -66,6 +83,11 @@ struct abx500_chargalg_suspension_status {
bool usb_suspended; bool usb_suspended;
}; };
struct abx500_chargalg_current_step_status {
bool curr_step_change;
int curr_step;
};
struct abx500_chargalg_battery_data { struct abx500_chargalg_battery_data {
int temp; int temp;
int volt; int volt;
@ -82,6 +104,7 @@ enum abx500_chargalg_states {
STATE_HW_TEMP_PROTECT_INIT, STATE_HW_TEMP_PROTECT_INIT,
STATE_HW_TEMP_PROTECT, STATE_HW_TEMP_PROTECT,
STATE_NORMAL_INIT, STATE_NORMAL_INIT,
STATE_USB_PP_PRE_CHARGE,
STATE_NORMAL, STATE_NORMAL,
STATE_WAIT_FOR_RECHARGE_INIT, STATE_WAIT_FOR_RECHARGE_INIT,
STATE_WAIT_FOR_RECHARGE, STATE_WAIT_FOR_RECHARGE,
@ -113,6 +136,7 @@ static const char *states[] = {
"HW_TEMP_PROTECT_INIT", "HW_TEMP_PROTECT_INIT",
"HW_TEMP_PROTECT", "HW_TEMP_PROTECT",
"NORMAL_INIT", "NORMAL_INIT",
"USB_PP_PRE_CHARGE",
"NORMAL", "NORMAL",
"WAIT_FOR_RECHARGE_INIT", "WAIT_FOR_RECHARGE_INIT",
"WAIT_FOR_RECHARGE", "WAIT_FOR_RECHARGE",
@ -204,6 +228,8 @@ enum maxim_ret {
* @batt_data: data of the battery * @batt_data: data of the battery
* @susp_status: current charger suspension status * @susp_status: current charger suspension status
* @bm: Platform specific battery management information * @bm: Platform specific battery management information
* @curr_status: Current step status for over-current protection
* @parent: pointer to the struct abx500
* @chargalg_psy: structure that holds the battery properties exposed by * @chargalg_psy: structure that holds the battery properties exposed by
* the charging algorithm * the charging algorithm
* @events: structure for information about events triggered * @events: structure for information about events triggered
@ -227,6 +253,8 @@ struct abx500_chargalg {
struct abx500_chargalg_charger_info chg_info; struct abx500_chargalg_charger_info chg_info;
struct abx500_chargalg_battery_data batt_data; struct abx500_chargalg_battery_data batt_data;
struct abx500_chargalg_suspension_status susp_status; struct abx500_chargalg_suspension_status susp_status;
struct ab8500 *parent;
struct abx500_chargalg_current_step_status curr_status;
struct abx500_bm_data *bm; struct abx500_bm_data *bm;
struct power_supply chargalg_psy; struct power_supply chargalg_psy;
struct ux500_charger *ac_chg; struct ux500_charger *ac_chg;
@ -236,51 +264,69 @@ struct abx500_chargalg {
struct delayed_work chargalg_periodic_work; struct delayed_work chargalg_periodic_work;
struct delayed_work chargalg_wd_work; struct delayed_work chargalg_wd_work;
struct work_struct chargalg_work; struct work_struct chargalg_work;
struct timer_list safety_timer; struct hrtimer safety_timer;
struct timer_list maintenance_timer; struct hrtimer maintenance_timer;
struct kobject chargalg_kobject; struct kobject chargalg_kobject;
}; };
/*External charger prepare notifier*/
BLOCKING_NOTIFIER_HEAD(charger_notifier_list);
/* Main battery properties */ /* Main battery properties */
static enum power_supply_property abx500_chargalg_props[] = { static enum power_supply_property abx500_chargalg_props[] = {
POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_HEALTH,
}; };
struct abx500_chargalg_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct abx500_chargalg *, char *);
ssize_t (*store)(struct abx500_chargalg *, const char *, size_t);
};
/** /**
* abx500_chargalg_safety_timer_expired() - Expiration of the safety timer * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer
* @data: pointer to the abx500_chargalg structure * @timer: pointer to the hrtimer structure
* *
* This function gets called when the safety timer for the charger * This function gets called when the safety timer for the charger
* expires * expires
*/ */
static void abx500_chargalg_safety_timer_expired(unsigned long data) static enum hrtimer_restart
abx500_chargalg_safety_timer_expired(struct hrtimer *timer)
{ {
struct abx500_chargalg *di = (struct abx500_chargalg *) data; struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg,
safety_timer);
dev_err(di->dev, "Safety timer expired\n"); dev_err(di->dev, "Safety timer expired\n");
di->events.safety_timer_expired = true; di->events.safety_timer_expired = true;
/* Trigger execution of the algorithm instantly */ /* Trigger execution of the algorithm instantly */
queue_work(di->chargalg_wq, &di->chargalg_work); queue_work(di->chargalg_wq, &di->chargalg_work);
return HRTIMER_NORESTART;
} }
/** /**
* abx500_chargalg_maintenance_timer_expired() - Expiration of * abx500_chargalg_maintenance_timer_expired() - Expiration of
* the maintenance timer * the maintenance timer
* @i: pointer to the abx500_chargalg structure * @timer: pointer to the timer structure
* *
* This function gets called when the maintenence timer * This function gets called when the maintenence timer
* expires * expires
*/ */
static void abx500_chargalg_maintenance_timer_expired(unsigned long data) static enum hrtimer_restart
abx500_chargalg_maintenance_timer_expired(struct hrtimer *timer)
{ {
struct abx500_chargalg *di = (struct abx500_chargalg *) data; struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg,
maintenance_timer);
dev_dbg(di->dev, "Maintenance timer expired\n"); dev_dbg(di->dev, "Maintenance timer expired\n");
di->events.maintenance_timer_expired = true; di->events.maintenance_timer_expired = true;
/* Trigger execution of the algorithm instantly */ /* Trigger execution of the algorithm instantly */
queue_work(di->chargalg_wq, &di->chargalg_work); queue_work(di->chargalg_wq, &di->chargalg_work);
return HRTIMER_NORESTART;
} }
/** /**
@ -303,6 +349,30 @@ static void abx500_chargalg_state_to(struct abx500_chargalg *di,
di->charge_state = state; di->charge_state = state;
} }
static int abx500_chargalg_check_charger_enable(struct abx500_chargalg *di)
{
switch (di->charge_state) {
case STATE_NORMAL:
case STATE_MAINTENANCE_A:
case STATE_MAINTENANCE_B:
break;
default:
return 0;
}
if (di->chg_info.charger_type & USB_CHG) {
return di->usb_chg->ops.check_enable(di->usb_chg,
di->bm->bat_type[di->bm->batt_id].normal_vol_lvl,
di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
} else if ((di->chg_info.charger_type & AC_CHG) &&
!(di->ac_chg->external)) {
return di->ac_chg->ops.check_enable(di->ac_chg,
di->bm->bat_type[di->bm->batt_id].normal_vol_lvl,
di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
}
return 0;
}
/** /**
* abx500_chargalg_check_charger_connection() - Check charger connection change * abx500_chargalg_check_charger_connection() - Check charger connection change
* @di: pointer to the abx500_chargalg structure * @di: pointer to the abx500_chargalg structure
@ -347,6 +417,22 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)
return di->chg_info.conn_chg; return di->chg_info.conn_chg;
} }
/**
* abx500_chargalg_check_current_step_status() - Check charging current
* step status.
* @di: pointer to the abx500_chargalg structure
*
* This function will check if there is a change in the charging current step
* and change charge state accordingly.
*/
static void abx500_chargalg_check_current_step_status
(struct abx500_chargalg *di)
{
if (di->curr_status.curr_step_change)
abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
di->curr_status.curr_step_change = false;
}
/** /**
* abx500_chargalg_start_safety_timer() - Start charging safety timer * abx500_chargalg_start_safety_timer() - Start charging safety timer
* @di: pointer to the abx500_chargalg structure * @di: pointer to the abx500_chargalg structure
@ -356,19 +442,16 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)
*/ */
static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
{ {
unsigned long timer_expiration = 0; /* Charger-dependent expiration time in hours*/
int timer_expiration = 0;
switch (di->chg_info.charger_type) { switch (di->chg_info.charger_type) {
case AC_CHG: case AC_CHG:
timer_expiration = timer_expiration = di->bm->main_safety_tmr_h;
round_jiffies(jiffies +
(di->bm->main_safety_tmr_h * 3600 * HZ));
break; break;
case USB_CHG: case USB_CHG:
timer_expiration = timer_expiration = di->bm->usb_safety_tmr_h;
round_jiffies(jiffies +
(di->bm->usb_safety_tmr_h * 3600 * HZ));
break; break;
default: default:
@ -377,11 +460,10 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
} }
di->events.safety_timer_expired = false; di->events.safety_timer_expired = false;
di->safety_timer.expires = timer_expiration; hrtimer_set_expires_range(&di->safety_timer,
if (!timer_pending(&di->safety_timer)) ktime_set(timer_expiration * ONE_HOUR_IN_SECONDS, 0),
add_timer(&di->safety_timer); ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
else hrtimer_start_expires(&di->safety_timer, HRTIMER_MODE_REL);
mod_timer(&di->safety_timer, timer_expiration);
} }
/** /**
@ -392,8 +474,8 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
*/ */
static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)
{ {
di->events.safety_timer_expired = false; if (hrtimer_try_to_cancel(&di->safety_timer) >= 0)
del_timer(&di->safety_timer); di->events.safety_timer_expired = false;
} }
/** /**
@ -408,17 +490,11 @@ static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)
static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,
int duration) int duration)
{ {
unsigned long timer_expiration; hrtimer_set_expires_range(&di->maintenance_timer,
ktime_set(duration * ONE_HOUR_IN_SECONDS, 0),
/* Convert from hours to jiffies */ ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ));
di->events.maintenance_timer_expired = false; di->events.maintenance_timer_expired = false;
di->maintenance_timer.expires = timer_expiration; hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);
if (!timer_pending(&di->maintenance_timer))
add_timer(&di->maintenance_timer);
else
mod_timer(&di->maintenance_timer, timer_expiration);
} }
/** /**
@ -430,8 +506,8 @@ static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,
*/ */
static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di) static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di)
{ {
di->events.maintenance_timer_expired = false; if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0)
del_timer(&di->maintenance_timer); di->events.maintenance_timer_expired = false;
} }
/** /**
@ -477,6 +553,8 @@ static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)
static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,
int vset, int iset) int vset, int iset)
{ {
static int abx500_chargalg_ex_ac_enable_toggle;
if (!di->ac_chg || !di->ac_chg->ops.enable) if (!di->ac_chg || !di->ac_chg->ops.enable)
return -ENXIO; return -ENXIO;
@ -489,6 +567,14 @@ static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,
di->chg_info.ac_iset = iset; di->chg_info.ac_iset = iset;
di->chg_info.ac_vset = vset; di->chg_info.ac_vset = vset;
/* Enable external charger */
if (enable && di->ac_chg->external &&
!abx500_chargalg_ex_ac_enable_toggle) {
blocking_notifier_call_chain(&charger_notifier_list,
0, di->dev);
abx500_chargalg_ex_ac_enable_toggle++;
}
return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset);
} }
@ -520,6 +606,37 @@ static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable,
return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset);
} }
/**
* ab8540_chargalg_usb_pp_en() - Enable/ disable USB power path
* @di: pointer to the abx500_chargalg structure
* @enable: power path enable/disable
*
* The USB power path will be enable/ disable
*/
static int ab8540_chargalg_usb_pp_en(struct abx500_chargalg *di, bool enable)
{
if (!di->usb_chg || !di->usb_chg->ops.pp_enable)
return -ENXIO;
return di->usb_chg->ops.pp_enable(di->usb_chg, enable);
}
/**
* ab8540_chargalg_usb_pre_chg_en() - Enable/ disable USB pre-charge
* @di: pointer to the abx500_chargalg structure
* @enable: USB pre-charge enable/disable
*
* The USB USB pre-charge will be enable/ disable
*/
static int ab8540_chargalg_usb_pre_chg_en(struct abx500_chargalg *di,
bool enable)
{
if (!di->usb_chg || !di->usb_chg->ops.pre_chg_enable)
return -ENXIO;
return di->usb_chg->ops.pre_chg_enable(di->usb_chg, enable);
}
/** /**
* abx500_chargalg_update_chg_curr() - Update charger current * abx500_chargalg_update_chg_curr() - Update charger current
* @di: pointer to the abx500_chargalg structure * @di: pointer to the abx500_chargalg structure
@ -613,8 +730,6 @@ static void abx500_chargalg_hold_charging(struct abx500_chargalg *di)
static void abx500_chargalg_start_charging(struct abx500_chargalg *di, static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
int vset, int iset) int vset, int iset)
{ {
bool start_chargalg_wd = true;
switch (di->chg_info.charger_type) { switch (di->chg_info.charger_type) {
case AC_CHG: case AC_CHG:
dev_dbg(di->dev, dev_dbg(di->dev,
@ -632,12 +747,8 @@ static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
default: default:
dev_err(di->dev, "Unknown charger to charge from\n"); dev_err(di->dev, "Unknown charger to charge from\n");
start_chargalg_wd = false;
break; break;
} }
if (start_chargalg_wd && !delayed_work_pending(&di->chargalg_wd_work))
queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0);
} }
/** /**
@ -725,6 +836,9 @@ static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di)
di->batt_data.avg_curr > 0) { di->batt_data.avg_curr > 0) {
if (++di->eoc_cnt >= EOC_COND_CNT) { if (++di->eoc_cnt >= EOC_COND_CNT) {
di->eoc_cnt = 0; di->eoc_cnt = 0;
if ((di->chg_info.charger_type & USB_CHG) &&
(di->usb_chg->power_path))
ab8540_chargalg_usb_pp_en(di, true);
di->charge_status = POWER_SUPPLY_STATUS_FULL; di->charge_status = POWER_SUPPLY_STATUS_FULL;
di->maintenance_chg = true; di->maintenance_chg = true;
dev_dbg(di->dev, "EOC reached!\n"); dev_dbg(di->dev, "EOC reached!\n");
@ -1217,6 +1331,8 @@ static void abx500_chargalg_external_power_changed(struct power_supply *psy)
static void abx500_chargalg_algorithm(struct abx500_chargalg *di) static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
{ {
int charger_status; int charger_status;
int ret;
int curr_step_lvl;
/* Collect data from all power_supply class devices */ /* Collect data from all power_supply class devices */
class_for_each_device(power_supply_class, NULL, class_for_each_device(power_supply_class, NULL,
@ -1227,6 +1343,15 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
abx500_chargalg_check_charger_voltage(di); abx500_chargalg_check_charger_voltage(di);
charger_status = abx500_chargalg_check_charger_connection(di); charger_status = abx500_chargalg_check_charger_connection(di);
abx500_chargalg_check_current_step_status(di);
if (is_ab8500(di->parent)) {
ret = abx500_chargalg_check_charger_enable(di);
if (ret < 0)
dev_err(di->dev, "Checking charger is enabled error"
": Returned Value %d\n", ret);
}
/* /*
* First check if we have a charger connected. * First check if we have a charger connected.
* Also we don't allow charging of unknown batteries if configured * Also we don't allow charging of unknown batteries if configured
@ -1416,9 +1541,34 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
break; break;
case STATE_NORMAL_INIT: case STATE_NORMAL_INIT:
abx500_chargalg_start_charging(di, if ((di->chg_info.charger_type & USB_CHG) &&
di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, di->usb_chg->power_path) {
di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); if (di->batt_data.volt >
(di->bm->fg_params->lowbat_threshold +
BAT_PLUS_MARGIN)) {
ab8540_chargalg_usb_pre_chg_en(di, false);
ab8540_chargalg_usb_pp_en(di, false);
} else {
ab8540_chargalg_usb_pp_en(di, true);
ab8540_chargalg_usb_pre_chg_en(di, true);
abx500_chargalg_state_to(di,
STATE_USB_PP_PRE_CHARGE);
break;
}
}
if (di->curr_status.curr_step == CHARGALG_CURR_STEP_LOW)
abx500_chargalg_stop_charging(di);
else {
curr_step_lvl = di->bm->bat_type[
di->bm->batt_id].normal_cur_lvl
* di->curr_status.curr_step
/ CHARGALG_CURR_STEP_HIGH;
abx500_chargalg_start_charging(di,
di->bm->bat_type[di->bm->batt_id]
.normal_vol_lvl, curr_step_lvl);
}
abx500_chargalg_state_to(di, STATE_NORMAL); abx500_chargalg_state_to(di, STATE_NORMAL);
abx500_chargalg_start_safety_timer(di); abx500_chargalg_start_safety_timer(di);
abx500_chargalg_stop_maintenance_timer(di); abx500_chargalg_stop_maintenance_timer(di);
@ -1430,6 +1580,13 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
break; break;
case STATE_USB_PP_PRE_CHARGE:
if (di->batt_data.volt >
(di->bm->fg_params->lowbat_threshold +
BAT_PLUS_MARGIN))
abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
break;
case STATE_NORMAL: case STATE_NORMAL:
handle_maxim_chg_curr(di); handle_maxim_chg_curr(di);
if (di->charge_status == POWER_SUPPLY_STATUS_FULL && if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
@ -1653,99 +1810,134 @@ static int abx500_chargalg_get_property(struct power_supply *psy,
/* Exposure to the sysfs interface */ /* Exposure to the sysfs interface */
/** static ssize_t abx500_chargalg_curr_step_show(struct abx500_chargalg *di,
* abx500_chargalg_sysfs_show() - sysfs show operations char *buf)
* @kobj: pointer to the struct kobject
* @attr: pointer to the struct attribute
* @buf: buffer that holds the parameter to send to userspace
*
* Returns a buffer to be displayed in user space
*/
static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{ {
struct abx500_chargalg *di = container_of(kobj, return sprintf(buf, "%d\n", di->curr_status.curr_step);
struct abx500_chargalg, chargalg_kobject); }
static ssize_t abx500_chargalg_curr_step_store(struct abx500_chargalg *di,
const char *buf, size_t length)
{
long int param;
int ret;
ret = kstrtol(buf, 10, &param);
if (ret < 0)
return ret;
di->curr_status.curr_step = param;
if (di->curr_status.curr_step >= CHARGALG_CURR_STEP_LOW &&
di->curr_status.curr_step <= CHARGALG_CURR_STEP_HIGH) {
di->curr_status.curr_step_change = true;
queue_work(di->chargalg_wq, &di->chargalg_work);
} else
dev_info(di->dev, "Wrong current step\n"
"Enter 0. Disable AC/USB Charging\n"
"1--100. Set AC/USB charging current step\n"
"100. Enable AC/USB Charging\n");
return strlen(buf);
}
static ssize_t abx500_chargalg_en_show(struct abx500_chargalg *di,
char *buf)
{
return sprintf(buf, "%d\n", return sprintf(buf, "%d\n",
di->susp_status.ac_suspended && di->susp_status.ac_suspended &&
di->susp_status.usb_suspended); di->susp_status.usb_suspended);
} }
/** static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di,
* abx500_chargalg_sysfs_charger() - sysfs store operations const char *buf, size_t length)
* @kobj: pointer to the struct kobject
* @attr: pointer to the struct attribute
* @buf: buffer that holds the parameter passed from userspace
* @length: length of the parameter passed
*
* Returns length of the buffer(input taken from user space) on success
* else error code on failure
* The operation to be performed on passing the parameters from the user space.
*/
static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
struct attribute *attr, const char *buf, size_t length)
{ {
struct abx500_chargalg *di = container_of(kobj,
struct abx500_chargalg, chargalg_kobject);
long int param; long int param;
int ac_usb; int ac_usb;
int ret; int ret;
char entry = *attr->name;
switch (entry) { ret = kstrtol(buf, 10, &param);
case 'c': if (ret < 0)
ret = strict_strtol(buf, 10, &param); return ret;
if (ret < 0)
return ret;
ac_usb = param; ac_usb = param;
switch (ac_usb) { switch (ac_usb) {
case 0: case 0:
/* Disable charging */ /* Disable charging */
di->susp_status.ac_suspended = true; di->susp_status.ac_suspended = true;
di->susp_status.usb_suspended = true; di->susp_status.usb_suspended = true;
di->susp_status.suspended_change = true; di->susp_status.suspended_change = true;
/* Trigger a state change */ /* Trigger a state change */
queue_work(di->chargalg_wq, queue_work(di->chargalg_wq,
&di->chargalg_work); &di->chargalg_work);
break;
case 1:
/* Enable AC Charging */
di->susp_status.ac_suspended = false;
di->susp_status.suspended_change = true;
/* Trigger a state change */
queue_work(di->chargalg_wq,
&di->chargalg_work);
break;
case 2:
/* Enable USB charging */
di->susp_status.usb_suspended = false;
di->susp_status.suspended_change = true;
/* Trigger a state change */
queue_work(di->chargalg_wq,
&di->chargalg_work);
break;
default:
dev_info(di->dev, "Wrong input\n"
"Enter 0. Disable AC/USB Charging\n"
"1. Enable AC charging\n"
"2. Enable USB Charging\n");
};
break; break;
case 1:
/* Enable AC Charging */
di->susp_status.ac_suspended = false;
di->susp_status.suspended_change = true;
/* Trigger a state change */
queue_work(di->chargalg_wq,
&di->chargalg_work);
break;
case 2:
/* Enable USB charging */
di->susp_status.usb_suspended = false;
di->susp_status.suspended_change = true;
/* Trigger a state change */
queue_work(di->chargalg_wq,
&di->chargalg_work);
break;
default:
dev_info(di->dev, "Wrong input\n"
"Enter 0. Disable AC/USB Charging\n"
"1. Enable AC charging\n"
"2. Enable USB Charging\n");
}; };
return strlen(buf); return strlen(buf);
} }
static struct attribute abx500_chargalg_en_charger = \ static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger =
__ATTR(chargalg, 0644, abx500_chargalg_en_show,
abx500_chargalg_en_store);
static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step =
__ATTR(chargalg_curr_step, 0644, abx500_chargalg_curr_step_show,
abx500_chargalg_curr_step_store);
static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{ {
.name = "chargalg", struct abx500_chargalg_sysfs_entry *entry = container_of(attr,
.mode = S_IRUGO | S_IWUSR, struct abx500_chargalg_sysfs_entry, attr);
};
struct abx500_chargalg *di = container_of(kobj,
struct abx500_chargalg, chargalg_kobject);
if (!entry->show)
return -EIO;
return entry->show(di, buf);
}
static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
struct attribute *attr, const char *buf, size_t length)
{
struct abx500_chargalg_sysfs_entry *entry = container_of(attr,
struct abx500_chargalg_sysfs_entry, attr);
struct abx500_chargalg *di = container_of(kobj,
struct abx500_chargalg, chargalg_kobject);
if (!entry->store)
return -EIO;
return entry->store(di, buf, length);
}
static struct attribute *abx500_chargalg_chg[] = { static struct attribute *abx500_chargalg_chg[] = {
&abx500_chargalg_en_charger, &abx500_chargalg_en_charger.attr,
NULL &abx500_chargalg_curr_step.attr,
NULL,
}; };
static const struct sysfs_ops abx500_chargalg_sysfs_ops = { static const struct sysfs_ops abx500_chargalg_sysfs_ops = {
@ -1832,10 +2024,16 @@ static int abx500_chargalg_remove(struct platform_device *pdev)
/* sysfs interface to enable/disbale charging from user space */ /* sysfs interface to enable/disbale charging from user space */
abx500_chargalg_sysfs_exit(di); abx500_chargalg_sysfs_exit(di);
hrtimer_cancel(&di->safety_timer);
hrtimer_cancel(&di->maintenance_timer);
cancel_delayed_work_sync(&di->chargalg_periodic_work);
cancel_delayed_work_sync(&di->chargalg_wd_work);
cancel_work_sync(&di->chargalg_work);
/* Delete the work queue */ /* Delete the work queue */
destroy_workqueue(di->chargalg_wq); destroy_workqueue(di->chargalg_wq);
flush_scheduled_work();
power_supply_unregister(&di->chargalg_psy); power_supply_unregister(&di->chargalg_psy);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
@ -1873,8 +2071,9 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
} }
} }
/* get device struct */ /* get device struct and parent */
di->dev = &pdev->dev; di->dev = &pdev->dev;
di->parent = dev_get_drvdata(pdev->dev.parent);
/* chargalg supply */ /* chargalg supply */
di->chargalg_psy.name = "abx500_chargalg"; di->chargalg_psy.name = "abx500_chargalg";
@ -1888,15 +2087,13 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
abx500_chargalg_external_power_changed; abx500_chargalg_external_power_changed;
/* Initilialize safety timer */ /* Initilialize safety timer */
init_timer(&di->safety_timer); hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
di->safety_timer.function = abx500_chargalg_safety_timer_expired; di->safety_timer.function = abx500_chargalg_safety_timer_expired;
di->safety_timer.data = (unsigned long) di;
/* Initilialize maintenance timer */ /* Initilialize maintenance timer */
init_timer(&di->maintenance_timer); hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
di->maintenance_timer.function = di->maintenance_timer.function =
abx500_chargalg_maintenance_timer_expired; abx500_chargalg_maintenance_timer_expired;
di->maintenance_timer.data = (unsigned long) di;
/* Create a work queue for the chargalg */ /* Create a work queue for the chargalg */
di->chargalg_wq = di->chargalg_wq =
@ -1933,6 +2130,7 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
dev_err(di->dev, "failed to create sysfs entry\n"); dev_err(di->dev, "failed to create sysfs entry\n");
goto free_psy; goto free_psy;
} }
di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH;
/* Run the charging algorithm */ /* Run the charging algorithm */
queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
@ -1964,18 +2162,7 @@ static struct platform_driver abx500_chargalg_driver = {
}, },
}; };
static int __init abx500_chargalg_init(void) module_platform_driver(abx500_chargalg_driver);
{
return platform_driver_register(&abx500_chargalg_driver);
}
static void __exit abx500_chargalg_exit(void)
{
platform_driver_unregister(&abx500_chargalg_driver);
}
module_init(abx500_chargalg_init);
module_exit(abx500_chargalg_exit);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); MODULE_AUTHOR("Johan Palsson, Karl Komierowski");

View File

@ -16,24 +16,24 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/power_supply.h> #include <linux/power_supply.h>
#include <linux/completion.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/kobject.h>
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500/ab8500.h>
#include <linux/mfd/abx500/ab8500-bm.h> #include <linux/mfd/abx500/ab8500-bm.h>
#include <linux/mfd/abx500/ab8500-gpadc.h>
#include <linux/mfd/abx500/ux500_chargalg.h> #include <linux/mfd/abx500/ux500_chargalg.h>
#include <linux/pm2301_charger.h> #include <linux/pm2301_charger.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/pm_runtime.h>
#include "pm2301_charger.h" #include "pm2301_charger.h"
#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \ #define to_pm2xxx_charger_ac_device_info(x) container_of((x), \
struct pm2xxx_charger, ac_chg) struct pm2xxx_charger, ac_chg)
#define SLEEP_MIN 50
#define SLEEP_MAX 100
#define PM2XXX_AUTOSUSPEND_DELAY 500
static int pm2xxx_interrupt_registers[] = { static int pm2xxx_interrupt_registers[] = {
PM2XXX_REG_INT1, PM2XXX_REG_INT1,
@ -113,33 +113,24 @@ static const struct i2c_device_id pm2xxx_ident[] = {
static void set_lpn_pin(struct pm2xxx_charger *pm2) static void set_lpn_pin(struct pm2xxx_charger *pm2)
{ {
if (pm2->ac.charger_connected) if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) {
return; gpio_set_value(pm2->lpn_pin, 1);
gpio_set_value(pm2->lpn_pin, 1); usleep_range(SLEEP_MIN, SLEEP_MAX);
}
return;
} }
static void clear_lpn_pin(struct pm2xxx_charger *pm2) static void clear_lpn_pin(struct pm2xxx_charger *pm2)
{ {
if (pm2->ac.charger_connected) if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin))
return; gpio_set_value(pm2->lpn_pin, 0);
gpio_set_value(pm2->lpn_pin, 0);
return;
} }
static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val)
{ {
int ret; int ret;
/*
* When AC adaptor is unplugged, the host /* wake up the device */
* must put LPN high to be able to pm_runtime_get_sync(pm2->dev);
* communicate by I2C with PM2301
* and receive I2C "acknowledge" from PM2301.
*/
mutex_lock(&pm2->lock);
set_lpn_pin(pm2);
ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg, ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
1, val); 1, val);
@ -147,8 +138,8 @@ static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val)
dev_err(pm2->dev, "Error reading register at 0x%x\n", reg); dev_err(pm2->dev, "Error reading register at 0x%x\n", reg);
else else
ret = 0; ret = 0;
clear_lpn_pin(pm2);
mutex_unlock(&pm2->lock); pm_runtime_put_sync(pm2->dev);
return ret; return ret;
} }
@ -156,14 +147,9 @@ static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val)
static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val) static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val)
{ {
int ret; int ret;
/*
* When AC adaptor is unplugged, the host /* wake up the device */
* must put LPN high to be able to pm_runtime_get_sync(pm2->dev);
* communicate by I2C with PM2301
* and receive I2C "acknowledge" from PM2301.
*/
mutex_lock(&pm2->lock);
set_lpn_pin(pm2);
ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg, ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
1, &val); 1, &val);
@ -171,8 +157,8 @@ static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val)
dev_err(pm2->dev, "Error writing register at 0x%x\n", reg); dev_err(pm2->dev, "Error writing register at 0x%x\n", reg);
else else
ret = 0; ret = 0;
clear_lpn_pin(pm2);
mutex_unlock(&pm2->lock); pm_runtime_put_sync(pm2->dev);
return ret; return ret;
} }
@ -192,11 +178,22 @@ static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2)
{ {
int ret; int ret;
/* Disable SW EOC ctrl */
ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW);
if (ret < 0) {
dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
return ret;
}
/* Disable charging */ /* Disable charging */
ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2,
(PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS)); (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS));
if (ret < 0) {
dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
return ret;
}
return ret; return 0;
} }
static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val) static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val)
@ -216,21 +213,14 @@ int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val)
static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val) static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val)
{ {
int ret = 0; dev_err(pm2->dev, "Overvoltage detected\n");
pm2->flags.ovv = true;
power_supply_changed(&pm2->ac_chg.psy);
pm2->failure_input_ovv++; /* Schedule a new HW failure check */
if (pm2->failure_input_ovv < 4) { queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0);
ret = pm2xxx_charging_enable_mngt(pm2);
goto out;
} else {
pm2->failure_input_ovv = 0;
dev_err(pm2->dev, "Overvoltage detected\n");
pm2->flags.ovv = true;
power_supply_changed(&pm2->ac_chg.psy);
}
out: return 0;
return ret;
} }
static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val) static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val)
@ -245,13 +235,29 @@ static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val)
static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val) static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val)
{ {
int ret;
switch (val) { switch (val) {
case PM2XXX_INT1_ITVBATLOWR: case PM2XXX_INT1_ITVBATLOWR:
dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n"); dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n");
/* Enable SW EOC ctrl */
ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG,
PM2XXX_SWCTRL_SW);
if (ret < 0) {
dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
return ret;
}
break; break;
case PM2XXX_INT1_ITVBATLOWF: case PM2XXX_INT1_ITVBATLOWF:
dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n"); dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n");
/* Disable SW EOC ctrl */
ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG,
PM2XXX_SWCTRL_HW);
if (ret < 0) {
dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
return ret;
}
break; break;
default: default:
@ -322,16 +328,27 @@ static int pm2_int_reg0(void *pm2_data, int val)
struct pm2xxx_charger *pm2 = pm2_data; struct pm2xxx_charger *pm2 = pm2_data;
int ret = 0; int ret = 0;
if (val & (PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF)) { if (val & PM2XXX_INT1_ITVBATLOWR) {
ret = pm2xxx_charger_vbat_lsig_mngt(pm2, val & ret = pm2xxx_charger_vbat_lsig_mngt(pm2,
(PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF)); PM2XXX_INT1_ITVBATLOWR);
if (ret < 0)
goto out;
}
if (val & PM2XXX_INT1_ITVBATLOWF) {
ret = pm2xxx_charger_vbat_lsig_mngt(pm2,
PM2XXX_INT1_ITVBATLOWF);
if (ret < 0)
goto out;
} }
if (val & PM2XXX_INT1_ITVBATDISCONNECT) { if (val & PM2XXX_INT1_ITVBATDISCONNECT) {
ret = pm2xxx_charger_bat_disc_mngt(pm2, ret = pm2xxx_charger_bat_disc_mngt(pm2,
PM2XXX_INT1_ITVBATDISCONNECT); PM2XXX_INT1_ITVBATDISCONNECT);
if (ret < 0)
goto out;
} }
out:
return ret; return ret;
} }
@ -447,7 +464,6 @@ static int pm2_int_reg5(void *pm2_data, int val)
struct pm2xxx_charger *pm2 = pm2_data; struct pm2xxx_charger *pm2 = pm2_data;
int ret = 0; int ret = 0;
if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) { if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) {
dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n"); dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n");
} }
@ -468,14 +484,22 @@ static irqreturn_t pm2xxx_irq_int(int irq, void *data)
struct pm2xxx_interrupts *interrupt = pm2->pm2_int; struct pm2xxx_interrupts *interrupt = pm2->pm2_int;
int i; int i;
for (i = 0; i < PM2XXX_NUM_INT_REG; i++) { /* wake up the device */
pm2xxx_reg_read(pm2, pm_runtime_get_sync(pm2->dev);
do {
for (i = 0; i < PM2XXX_NUM_INT_REG; i++) {
pm2xxx_reg_read(pm2,
pm2xxx_interrupt_registers[i], pm2xxx_interrupt_registers[i],
&(interrupt->reg[i])); &(interrupt->reg[i]));
if (interrupt->reg[i] > 0) if (interrupt->reg[i] > 0)
interrupt->handler[i](pm2, interrupt->reg[i]); interrupt->handler[i](pm2, interrupt->reg[i]);
} }
} while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0);
pm_runtime_mark_last_busy(pm2->dev);
pm_runtime_put_autosuspend(pm2->dev);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
@ -592,6 +616,8 @@ static int pm2xxx_charger_ac_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_HEALTH_DEAD; val->intval = POWER_SUPPLY_HEALTH_DEAD;
else if (pm2->flags.main_thermal_prot) else if (pm2->flags.main_thermal_prot)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (pm2->flags.ovv)
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else else
val->intval = POWER_SUPPLY_HEALTH_GOOD; val->intval = POWER_SUPPLY_HEALTH_GOOD;
break; break;
@ -674,10 +700,6 @@ static int pm2xxx_charging_init(struct pm2xxx_charger *pm2)
ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG, ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG,
PM2XXX_VBAT_LOW_MONITORING_ENA); PM2XXX_VBAT_LOW_MONITORING_ENA);
/* Disable LED */
ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG,
PM2XXX_LED_SELECT_DIS);
return ret; return ret;
} }
@ -822,10 +844,54 @@ static void pm2xxx_charger_ac_work(struct work_struct *work)
sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present"); sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present");
}; };
static void pm2xxx_charger_check_hw_failure_work(struct work_struct *work)
{
u8 reg_value;
struct pm2xxx_charger *pm2 = container_of(work,
struct pm2xxx_charger, check_hw_failure_work.work);
if (pm2->flags.ovv) {
pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &reg_value);
if (!(reg_value & (PM2XXX_INT4_S_ITVPWR1OVV |
PM2XXX_INT4_S_ITVPWR2OVV))) {
pm2->flags.ovv = false;
power_supply_changed(&pm2->ac_chg.psy);
}
}
/* If we still have a failure, schedule a new check */
if (pm2->flags.ovv) {
queue_delayed_work(pm2->charger_wq,
&pm2->check_hw_failure_work, round_jiffies(HZ));
}
}
static void pm2xxx_charger_check_main_thermal_prot_work( static void pm2xxx_charger_check_main_thermal_prot_work(
struct work_struct *work) struct work_struct *work)
{ {
}; int ret;
u8 val;
struct pm2xxx_charger *pm2 = container_of(work, struct pm2xxx_charger,
check_main_thermal_prot_work);
/* Check if die temp warning is still active */
ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT5, &val);
if (ret < 0) {
dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
return;
}
if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGRISE
| PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE))
pm2->flags.main_thermal_prot = true;
else if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGFALL
| PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL))
pm2->flags.main_thermal_prot = false;
power_supply_changed(&pm2->ac_chg.psy);
}
static struct pm2xxx_interrupts pm2xxx_int = { static struct pm2xxx_interrupts pm2xxx_int = {
.handler[0] = pm2_int_reg0, .handler[0] = pm2_int_reg0,
@ -842,22 +908,92 @@ static struct pm2xxx_irq pm2xxx_charger_irq[] = {
static int pm2xxx_wall_charger_resume(struct i2c_client *i2c_client) static int pm2xxx_wall_charger_resume(struct i2c_client *i2c_client)
{ {
struct pm2xxx_charger *pm2;
pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client);
set_lpn_pin(pm2);
/* If we still have a HW failure, schedule a new check */
if (pm2->flags.ovv)
queue_delayed_work(pm2->charger_wq,
&pm2->check_hw_failure_work, 0);
return 0; return 0;
} }
static int pm2xxx_wall_charger_suspend(struct i2c_client *i2c_client, static int pm2xxx_wall_charger_suspend(struct i2c_client *i2c_client,
pm_message_t state) pm_message_t state)
{ {
struct pm2xxx_charger *pm2;
pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client);
clear_lpn_pin(pm2);
/* Cancel any pending HW failure check */
if (delayed_work_pending(&pm2->check_hw_failure_work))
cancel_delayed_work(&pm2->check_hw_failure_work);
flush_work(&pm2->ac_work);
flush_work(&pm2->check_main_thermal_prot_work);
return 0; return 0;
} }
static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, #ifdef CONFIG_PM
static int pm2xxx_runtime_suspend(struct device *dev)
{
struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev);
struct pm2xxx_charger *pm2;
int ret = 0;
pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client);
if (!pm2) {
dev_err(pm2->dev, "no pm2xxx_charger data supplied\n");
ret = -EINVAL;
return ret;
}
clear_lpn_pin(pm2);
return ret;
}
static int pm2xxx_runtime_resume(struct device *dev)
{
struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev);
struct pm2xxx_charger *pm2;
int ret = 0;
pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client);
if (!pm2) {
dev_err(pm2->dev, "no pm2xxx_charger data supplied\n");
ret = -EINVAL;
return ret;
}
if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0)
set_lpn_pin(pm2);
return ret;
}
static const struct dev_pm_ops pm2xxx_pm_ops = {
.runtime_suspend = pm2xxx_runtime_suspend,
.runtime_resume = pm2xxx_runtime_resume,
};
#define PM2XXX_PM_OPS (&pm2xxx_pm_ops)
#else
#define PM2XXX_PM_OPS NULL
#endif
static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
const struct i2c_device_id *id) const struct i2c_device_id *id)
{ {
struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data; struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data;
struct pm2xxx_charger *pm2; struct pm2xxx_charger *pm2;
int ret = 0; int ret = 0;
u8 val; u8 val;
int i;
pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL); pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL);
if (!pm2) { if (!pm2) {
@ -867,7 +1003,6 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
/* get parent data */ /* get parent data */
pm2->dev = &i2c_client->dev; pm2->dev = &i2c_client->dev;
pm2->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
pm2->pm2_int = &pm2xxx_int; pm2->pm2_int = &pm2xxx_int;
@ -889,14 +1024,6 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
pm2->bat = pl_data->battery; pm2->bat = pl_data->battery;
/*get lpn GPIO from platform data*/
if (!pm2->pdata->lpn_gpio) {
dev_err(pm2->dev, "no lpn gpio data supplied\n");
ret = -EINVAL;
goto free_device_info;
}
pm2->lpn_pin = pm2->pdata->lpn_gpio;
if (!i2c_check_functionality(i2c_client->adapter, if (!i2c_check_functionality(i2c_client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_READ_WORD_DATA)) { I2C_FUNC_SMBUS_READ_WORD_DATA)) {
@ -945,6 +1072,10 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
INIT_WORK(&pm2->check_main_thermal_prot_work, INIT_WORK(&pm2->check_main_thermal_prot_work,
pm2xxx_charger_check_main_thermal_prot_work); pm2xxx_charger_check_main_thermal_prot_work);
/* Init work for HW failure check */
INIT_DEFERRABLE_WORK(&pm2->check_hw_failure_work,
pm2xxx_charger_check_hw_failure_work);
/* /*
* VDD ADC supply needs to be enabled from this driver when there * VDD ADC supply needs to be enabled from this driver when there
* is a charger connected to avoid erroneous BTEMP_HIGH/LOW * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
@ -965,40 +1096,72 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
} }
/* Register interrupts */ /* Register interrupts */
ret = request_threaded_irq(pm2->pdata->irq_number, NULL, ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number),
NULL,
pm2xxx_charger_irq[0].isr, pm2xxx_charger_irq[0].isr,
pm2->pdata->irq_type, pm2->pdata->irq_type,
pm2xxx_charger_irq[0].name, pm2); pm2xxx_charger_irq[0].name, pm2);
if (ret != 0) { if (ret != 0) {
dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n", dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n",
pm2xxx_charger_irq[0].name, pm2->pdata->irq_number, ret); pm2xxx_charger_irq[0].name,
gpio_to_irq(pm2->pdata->gpio_irq_number), ret);
goto unregister_pm2xxx_charger; goto unregister_pm2xxx_charger;
} }
/*Initialize lock*/ ret = pm_runtime_set_active(pm2->dev);
if (ret)
dev_err(pm2->dev, "set active Error\n");
pm_runtime_enable(pm2->dev);
pm_runtime_set_autosuspend_delay(pm2->dev, PM2XXX_AUTOSUSPEND_DELAY);
pm_runtime_use_autosuspend(pm2->dev);
pm_runtime_resume(pm2->dev);
/* pm interrupt can wake up system */
ret = enable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
if (ret) {
dev_err(pm2->dev, "failed to set irq wake\n");
goto unregister_pm2xxx_interrupt;
}
mutex_init(&pm2->lock); mutex_init(&pm2->lock);
/* if (gpio_is_valid(pm2->pdata->lpn_gpio)) {
* Charger detection mechanism requires pulling up the LPN pin /* get lpn GPIO from platform data */
* while i2c communication if Charger is not connected pm2->lpn_pin = pm2->pdata->lpn_gpio;
* LPN pin of PM2301 is GPIO60 of AB9540
*/ /*
ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio"); * Charger detection mechanism requires pulling up the LPN pin
if (ret < 0) { * while i2c communication if Charger is not connected
dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n"); * LPN pin of PM2301 is GPIO60 of AB9540
goto unregister_pm2xxx_charger; */
} ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio");
ret = gpio_direction_output(pm2->lpn_pin, 0);
if (ret < 0) { if (ret < 0) {
dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n"); dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n");
goto free_gpio; goto disable_pm2_irq_wake;
}
ret = gpio_direction_output(pm2->lpn_pin, 0);
if (ret < 0) {
dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n");
goto free_gpio;
}
set_lpn_pin(pm2);
} }
/* read interrupt registers */
for (i = 0; i < PM2XXX_NUM_INT_REG; i++)
pm2xxx_reg_read(pm2,
pm2xxx_interrupt_registers[i],
&val);
ret = pm2xxx_charger_detection(pm2, &val); ret = pm2xxx_charger_detection(pm2, &val);
if ((ret == 0) && val) { if ((ret == 0) && val) {
pm2->ac.charger_connected = 1; pm2->ac.charger_connected = 1;
ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON,
AB8500_MAIN_CH_DET);
pm2->ac_conn = true; pm2->ac_conn = true;
power_supply_changed(&pm2->ac_chg.psy); power_supply_changed(&pm2->ac_chg.psy);
sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present"); sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present");
@ -1007,7 +1170,13 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
return 0; return 0;
free_gpio: free_gpio:
gpio_free(pm2->lpn_pin); if (gpio_is_valid(pm2->lpn_pin))
gpio_free(pm2->lpn_pin);
disable_pm2_irq_wake:
disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
unregister_pm2xxx_interrupt:
/* disable interrupt */
free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2);
unregister_pm2xxx_charger: unregister_pm2xxx_charger:
/* unregister power supply */ /* unregister power supply */
power_supply_unregister(&pm2->ac_chg.psy); power_supply_unregister(&pm2->ac_chg.psy);
@ -1018,18 +1187,24 @@ free_charger_wq:
destroy_workqueue(pm2->charger_wq); destroy_workqueue(pm2->charger_wq);
free_device_info: free_device_info:
kfree(pm2); kfree(pm2);
return ret; return ret;
} }
static int __devexit pm2xxx_wall_charger_remove(struct i2c_client *i2c_client) static int pm2xxx_wall_charger_remove(struct i2c_client *i2c_client)
{ {
struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client); struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client);
/* Disable pm_runtime */
pm_runtime_disable(pm2->dev);
/* Disable AC charging */ /* Disable AC charging */
pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0); pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0);
/* Disable wake by pm interrupt */
disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
/* Disable interrupts */ /* Disable interrupts */
free_irq(pm2->pdata->irq_number, pm2); free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2);
/* Delete the work queue */ /* Delete the work queue */
destroy_workqueue(pm2->charger_wq); destroy_workqueue(pm2->charger_wq);
@ -1041,8 +1216,8 @@ static int __devexit pm2xxx_wall_charger_remove(struct i2c_client *i2c_client)
power_supply_unregister(&pm2->ac_chg.psy); power_supply_unregister(&pm2->ac_chg.psy);
/*Free GPIO60*/ if (gpio_is_valid(pm2->lpn_pin))
gpio_free(pm2->lpn_pin); gpio_free(pm2->lpn_pin);
kfree(pm2); kfree(pm2);
@ -1058,12 +1233,13 @@ MODULE_DEVICE_TABLE(i2c, pm2xxx_id);
static struct i2c_driver pm2xxx_charger_driver = { static struct i2c_driver pm2xxx_charger_driver = {
.probe = pm2xxx_wall_charger_probe, .probe = pm2xxx_wall_charger_probe,
.remove = __devexit_p(pm2xxx_wall_charger_remove), .remove = pm2xxx_wall_charger_remove,
.suspend = pm2xxx_wall_charger_suspend, .suspend = pm2xxx_wall_charger_suspend,
.resume = pm2xxx_wall_charger_resume, .resume = pm2xxx_wall_charger_resume,
.driver = { .driver = {
.name = "pm2xxx-wall_charger", .name = "pm2xxx-wall_charger",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.pm = PM2XXX_PM_OPS,
}, },
.id_table = pm2xxx_id, .id_table = pm2xxx_id,
}; };
@ -1078,11 +1254,10 @@ static void __exit pm2xxx_charger_exit(void)
i2c_del_driver(&pm2xxx_charger_driver); i2c_del_driver(&pm2xxx_charger_driver);
} }
subsys_initcall_sync(pm2xxx_charger_init); device_initcall_sync(pm2xxx_charger_init);
module_exit(pm2xxx_charger_exit); module_exit(pm2xxx_charger_exit);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay"); MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay");
MODULE_ALIAS("platform:pm2xxx-charger"); MODULE_ALIAS("platform:pm2xxx-charger");
MODULE_DESCRIPTION("PM2xxx charger management driver"); MODULE_DESCRIPTION("PM2xxx charger management driver");

View File

@ -9,27 +9,6 @@
#ifndef PM2301_CHARGER_H #ifndef PM2301_CHARGER_H
#define PM2301_CHARGER_H #define PM2301_CHARGER_H
#define MAIN_WDOG_ENA 0x01
#define MAIN_WDOG_KICK 0x02
#define MAIN_WDOG_DIS 0x00
#define CHARG_WD_KICK 0x01
#define MAIN_CH_ENA 0x01
#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02
#define MAIN_CH_DET 0x01
#define MAIN_CH_CV_ON 0x04
#define OTP_ENABLE_WD 0x01
#define MAIN_CH_INPUT_CURR_SHIFT 4
#define LED_INDICATOR_PWM_ENA 0x01
#define LED_INDICATOR_PWM_DIS 0x00
#define LED_IND_CUR_5MA 0x04
#define LED_INDICATOR_PWM_DUTY_252_256 0xBF
/* HW failure constants */
#define MAIN_CH_TH_PROT 0x02
#define MAIN_CH_NOK 0x01
/* Watchdog timeout constant */ /* Watchdog timeout constant */
#define WD_TIMER 0x30 /* 4min */ #define WD_TIMER 0x30 /* 4min */
#define WD_KICK_INTERVAL (30 * HZ) #define WD_KICK_INTERVAL (30 * HZ)
@ -495,7 +474,6 @@ struct pm2xxx_charger {
int failure_input_ovv; int failure_input_ovv;
unsigned int lpn_pin; unsigned int lpn_pin;
struct pm2xxx_interrupts *pm2_int; struct pm2xxx_interrupts *pm2_int;
struct ab8500_gpadc *gpadc;
struct regulator *regu; struct regulator *regu;
struct pm2xxx_bm_data *bat; struct pm2xxx_bm_data *bat;
struct mutex lock; struct mutex lock;
@ -506,6 +484,7 @@ struct pm2xxx_charger {
struct delayed_work check_vbat_work; struct delayed_work check_vbat_work;
struct work_struct ac_work; struct work_struct ac_work;
struct work_struct check_main_thermal_prot_work; struct work_struct check_main_thermal_prot_work;
struct delayed_work check_hw_failure_work;
struct ux500_charger ac_chg; struct ux500_charger ac_chg;
struct pm2xxx_charger_event_flags flags; struct pm2xxx_charger_event_flags flags;
}; };

View File

@ -89,6 +89,11 @@ struct abx500_fg;
* points. * points.
* @maint_thres This is the threshold where we stop reporting * @maint_thres This is the threshold where we stop reporting
* battery full while in maintenance, in per cent * battery full while in maintenance, in per cent
* @pcut_enable: Enable power cut feature in ab8505
* @pcut_max_time: Max time threshold
* @pcut_flag_time: Flagtime threshold
* @pcut_max_restart: Max number of restarts
* @pcut_debounce_time: Sets battery debounce time
*/ */
struct abx500_fg_parameters { struct abx500_fg_parameters {
int recovery_sleep_timer; int recovery_sleep_timer;
@ -106,6 +111,11 @@ struct abx500_fg_parameters {
int battok_raising_th_sel1; int battok_raising_th_sel1;
int user_cap_limit; int user_cap_limit;
int maint_thres; int maint_thres;
bool pcut_enable;
u8 pcut_max_time;
u8 pcut_flag_time;
u8 pcut_max_restart;
u8 pcut_debounce_time;
}; };
/** /**
@ -236,7 +246,11 @@ struct abx500_bm_charger_parameters {
* @interval_not_charging charge alg cycle period time when not charging (sec) * @interval_not_charging charge alg cycle period time when not charging (sec)
* @temp_hysteresis temperature hysteresis * @temp_hysteresis temperature hysteresis
* @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm)
* @maxi: maximization parameters * @n_chg_out_curr number of elements in array chg_output_curr
* @n_chg_in_curr number of elements in array chg_input_curr
* @chg_output_curr charger output current level map
* @chg_input_curr charger input current level map
* @maxi maximization parameters
* @cap_levels capacity in percent for the different capacity levels * @cap_levels capacity in percent for the different capacity levels
* @bat_type table of supported battery types * @bat_type table of supported battery types
* @chg_params charger parameters * @chg_params charger parameters
@ -257,6 +271,7 @@ struct abx500_bm_data {
bool autopower_cfg; bool autopower_cfg;
bool ac_enabled; bool ac_enabled;
bool usb_enabled; bool usb_enabled;
bool usb_power_path;
bool no_maintenance; bool no_maintenance;
bool capacity_scaling; bool capacity_scaling;
bool chg_unknown_bat; bool chg_unknown_bat;
@ -270,6 +285,10 @@ struct abx500_bm_data {
int interval_not_charging; int interval_not_charging;
int temp_hysteresis; int temp_hysteresis;
int gnd_lift_resistance; int gnd_lift_resistance;
int n_chg_out_curr;
int n_chg_in_curr;
int *chg_output_curr;
int *chg_input_curr;
const struct abx500_maxim_parameters *maxi; const struct abx500_maxim_parameters *maxi;
const struct abx500_bm_capacity_levels *cap_levels; const struct abx500_bm_capacity_levels *cap_levels;
struct abx500_battery_type *bat_type; struct abx500_battery_type *bat_type;

View File

@ -23,6 +23,7 @@
* Bank : 0x5 * Bank : 0x5
*/ */
#define AB8500_USB_LINE_STAT_REG 0x80 #define AB8500_USB_LINE_STAT_REG 0x80
#define AB8500_USB_LINE_CTRL2_REG 0x82
#define AB8500_USB_LINK1_STAT_REG 0x94 #define AB8500_USB_LINK1_STAT_REG 0x94
/* /*
@ -33,7 +34,7 @@
#define AB8500_CH_STATUS2_REG 0x01 #define AB8500_CH_STATUS2_REG 0x01
#define AB8500_CH_USBCH_STAT1_REG 0x02 #define AB8500_CH_USBCH_STAT1_REG 0x02
#define AB8500_CH_USBCH_STAT2_REG 0x03 #define AB8500_CH_USBCH_STAT2_REG 0x03
#define AB8500_CH_FSM_STAT_REG 0x04 #define AB8540_CH_USBCH_STAT3_REG 0x04
#define AB8500_CH_STAT_REG 0x05 #define AB8500_CH_STAT_REG 0x05
/* /*
@ -69,6 +70,8 @@
#define AB8500_USBCH_CTRL1_REG 0xC0 #define AB8500_USBCH_CTRL1_REG 0xC0
#define AB8500_USBCH_CTRL2_REG 0xC1 #define AB8500_USBCH_CTRL2_REG 0xC1
#define AB8500_USBCH_IPT_CRNTLVL_REG 0xC2 #define AB8500_USBCH_IPT_CRNTLVL_REG 0xC2
#define AB8540_USB_PP_MODE_REG 0xC5
#define AB8540_USB_PP_CHR_REG 0xC6
/* /*
* Gas Gauge register offsets * Gas Gauge register offsets
@ -105,6 +108,7 @@
#define AB8500_RTC_BACKUP_CHG_REG 0x0C #define AB8500_RTC_BACKUP_CHG_REG 0x0C
#define AB8500_RTC_CC_CONF_REG 0x01 #define AB8500_RTC_CC_CONF_REG 0x01
#define AB8500_RTC_CTRL_REG 0x0B #define AB8500_RTC_CTRL_REG 0x0B
#define AB8500_RTC_CTRL1_REG 0x11
/* /*
* OTP register offsets * OTP register offsets
@ -154,6 +158,7 @@
#define CH_OP_CUR_LVL_1P4 0x0D #define CH_OP_CUR_LVL_1P4 0x0D
#define CH_OP_CUR_LVL_1P5 0x0E #define CH_OP_CUR_LVL_1P5 0x0E
#define CH_OP_CUR_LVL_1P6 0x0F #define CH_OP_CUR_LVL_1P6 0x0F
#define CH_OP_CUR_LVL_2P 0x3F
/* BTEMP High thermal limits */ /* BTEMP High thermal limits */
#define BTEMP_HIGH_TH_57_0 0x00 #define BTEMP_HIGH_TH_57_0 0x00
@ -179,10 +184,25 @@
#define BUP_ICH_SEL_300UA 0x08 #define BUP_ICH_SEL_300UA 0x08
#define BUP_ICH_SEL_700UA 0x0C #define BUP_ICH_SEL_700UA 0x0C
#define BUP_VCH_SEL_2P5V 0x00 enum bup_vch_sel {
#define BUP_VCH_SEL_2P6V 0x01 BUP_VCH_SEL_2P5V,
#define BUP_VCH_SEL_2P8V 0x02 BUP_VCH_SEL_2P6V,
#define BUP_VCH_SEL_3P1V 0x03 BUP_VCH_SEL_2P8V,
BUP_VCH_SEL_3P1V,
/*
* Note that the following 5 values 2.7v, 2.9v, 3.0v, 3.2v, 3.3v
* are only available on ab8540. You can't choose these 5
* voltage on ab8500/ab8505/ab9540.
*/
BUP_VCH_SEL_2P7V,
BUP_VCH_SEL_2P9V,
BUP_VCH_SEL_3P0V,
BUP_VCH_SEL_3P2V,
BUP_VCH_SEL_3P3V,
};
#define BUP_VCH_RANGE 0x02
#define VBUP33_VRTCN 0x01
/* Battery OVV constants */ /* Battery OVV constants */
#define BATT_OVV_ENA 0x02 #define BATT_OVV_ENA 0x02
@ -228,6 +248,8 @@
#define BAT_CTRL_20U_ENA 0x02 #define BAT_CTRL_20U_ENA 0x02
#define BAT_CTRL_18U_ENA 0x01 #define BAT_CTRL_18U_ENA 0x01
#define BAT_CTRL_16U_ENA 0x02 #define BAT_CTRL_16U_ENA 0x02
#define BAT_CTRL_60U_ENA 0x01
#define BAT_CTRL_120U_ENA 0x02
#define BAT_CTRL_CMP_ENA 0x04 #define BAT_CTRL_CMP_ENA 0x04
#define FORCE_BAT_CTRL_CMP_HIGH 0x08 #define FORCE_BAT_CTRL_CMP_HIGH 0x08
#define BAT_CTRL_PULL_UP_ENA 0x10 #define BAT_CTRL_PULL_UP_ENA 0x10
@ -235,6 +257,24 @@
/* Battery type */ /* Battery type */
#define BATTERY_UNKNOWN 00 #define BATTERY_UNKNOWN 00
/* Registers for pcut feature in ab8505 and ab9540 */
#define AB8505_RTC_PCUT_CTL_STATUS_REG 0x12
#define AB8505_RTC_PCUT_TIME_REG 0x13
#define AB8505_RTC_PCUT_MAX_TIME_REG 0x14
#define AB8505_RTC_PCUT_FLAG_TIME_REG 0x15
#define AB8505_RTC_PCUT_RESTART_REG 0x16
#define AB8505_RTC_PCUT_DEBOUNCE_REG 0x17
/* USB Power Path constants for ab8540 */
#define BUS_VSYS_VOL_SELECT_MASK 0x06
#define BUS_VSYS_VOL_SELECT_3P6V 0x00
#define BUS_VSYS_VOL_SELECT_3P325V 0x02
#define BUS_VSYS_VOL_SELECT_3P9V 0x04
#define BUS_VSYS_VOL_SELECT_4P3V 0x06
#define BUS_POWER_PATH_MODE_ENA 0x01
#define BUS_PP_PRECHG_CURRENT_MASK 0x0E
#define BUS_POWER_PATH_PRECHG_ENA 0x01
/** /**
* struct res_to_temp - defines one point in a temp to res curve. To * struct res_to_temp - defines one point in a temp to res curve. To
* be used in battery packs that combines the identification resistor with a * be used in battery packs that combines the identification resistor with a
@ -283,6 +323,11 @@ struct ab8500_fg;
* points. * points.
* @maint_thres This is the threshold where we stop reporting * @maint_thres This is the threshold where we stop reporting
* battery full while in maintenance, in per cent * battery full while in maintenance, in per cent
* @pcut_enable: Enable power cut feature in ab8505
* @pcut_max_time: Max time threshold
* @pcut_flag_time: Flagtime threshold
* @pcut_max_restart: Max number of restarts
* @pcut_debunce_time: Sets battery debounce time
*/ */
struct ab8500_fg_parameters { struct ab8500_fg_parameters {
int recovery_sleep_timer; int recovery_sleep_timer;
@ -299,6 +344,11 @@ struct ab8500_fg_parameters {
int battok_raising_th_sel1; int battok_raising_th_sel1;
int user_cap_limit; int user_cap_limit;
int maint_thres; int maint_thres;
bool pcut_enable;
u8 pcut_max_time;
u8 pcut_flag_time;
u8 pcut_max_restart;
u8 pcut_debunce_time;
}; };
/** /**

View File

@ -4,32 +4,72 @@
* *
* Author: Arun R Murthy <arun.murthy@stericsson.com> * Author: Arun R Murthy <arun.murthy@stericsson.com>
* Author: Daniel Willerud <daniel.willerud@stericsson.com> * Author: Daniel Willerud <daniel.willerud@stericsson.com>
* Author: M'boumba Cedric Madianga <cedric.madianga@stericsson.com>
*/ */
#ifndef _AB8500_GPADC_H #ifndef _AB8500_GPADC_H
#define _AB8500_GPADC_H #define _AB8500_GPADC_H
/* GPADC source: From datasheet(ADCSwSel[4:0] in GPADCCtrl2) */ /* GPADC source: From datasheet(ADCSwSel[4:0] in GPADCCtrl2
#define BAT_CTRL 0x01 * and ADCHwSel[4:0] in GPADCCtrl3 ) */
#define BTEMP_BALL 0x02 #define BAT_CTRL 0x01
#define MAIN_CHARGER_V 0x03 #define BTEMP_BALL 0x02
#define ACC_DETECT1 0x04 #define MAIN_CHARGER_V 0x03
#define ACC_DETECT2 0x05 #define ACC_DETECT1 0x04
#define ADC_AUX1 0x06 #define ACC_DETECT2 0x05
#define ADC_AUX2 0x07 #define ADC_AUX1 0x06
#define MAIN_BAT_V 0x08 #define ADC_AUX2 0x07
#define VBUS_V 0x09 #define MAIN_BAT_V 0x08
#define MAIN_CHARGER_C 0x0A #define VBUS_V 0x09
#define USB_CHARGER_C 0x0B #define MAIN_CHARGER_C 0x0A
#define BK_BAT_V 0x0C #define USB_CHARGER_C 0x0B
#define DIE_TEMP 0x0D #define BK_BAT_V 0x0C
#define DIE_TEMP 0x0D
#define USB_ID 0x0E
#define XTAL_TEMP 0x12
#define VBAT_TRUE_MEAS 0x13
#define BAT_CTRL_AND_IBAT 0x1C
#define VBAT_MEAS_AND_IBAT 0x1D
#define VBAT_TRUE_MEAS_AND_IBAT 0x1E
#define BAT_TEMP_AND_IBAT 0x1F
/* Virtual channel used only for ibat convertion to ampere
* Battery current conversion (ibat) cannot be requested as a single conversion
* but it is always in combination with other input requests
*/
#define IBAT_VIRTUAL_CHANNEL 0xFF
#define SAMPLE_1 1
#define SAMPLE_4 4
#define SAMPLE_8 8
#define SAMPLE_16 16
#define RISING_EDGE 0
#define FALLING_EDGE 1
/* Arbitrary ADC conversion type constants */
#define ADC_SW 0
#define ADC_HW 1
struct ab8500_gpadc; struct ab8500_gpadc;
struct ab8500_gpadc *ab8500_gpadc_get(char *name); struct ab8500_gpadc *ab8500_gpadc_get(char *name);
int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel); int ab8500_gpadc_sw_hw_convert(struct ab8500_gpadc *gpadc, u8 channel,
int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel); u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type);
static inline int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel)
{
return ab8500_gpadc_sw_hw_convert(gpadc, channel,
SAMPLE_16, 0, 0, ADC_SW);
}
int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel,
u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type);
int ab8500_gpadc_double_read_raw(struct ab8500_gpadc *gpadc, u8 channel,
u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type,
int *ibat);
int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc,
u8 channel, int ad_value); u8 channel, int ad_value);
void ab8540_gpadc_get_otp(struct ab8500_gpadc *gpadc,
u16 *vmain_l, u16 *vmain_h, u16 *btemp_l, u16 *btemp_h,
u16 *vbat_l, u16 *vbat_h, u16 *ibat_l, u16 *ibat_h);
#endif /* _AB8500_GPADC_H */ #endif /* _AB8500_GPADC_H */

View File

@ -12,6 +12,7 @@
int ab8500_sysctrl_read(u16 reg, u8 *value); int ab8500_sysctrl_read(u16 reg, u8 *value);
int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value); int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value);
void ab8500_restart(char mode, const char *cmd);
#else #else
@ -40,6 +41,7 @@ static inline int ab8500_sysctrl_clear(u16 reg, u8 bits)
/* Configuration data for SysClkReq1RfClkBuf - SysClkReq8RfClkBuf */ /* Configuration data for SysClkReq1RfClkBuf - SysClkReq8RfClkBuf */
struct ab8500_sysctrl_platform_data { struct ab8500_sysctrl_platform_data {
u8 initial_req_buf_config[8]; u8 initial_req_buf_config[8];
u16 (*reboot_reason_code)(const char *cmd);
}; };
/* Registers */ /* Registers */
@ -299,4 +301,8 @@ struct ab8500_sysctrl_platform_data {
#define AB9540_SYSCLK12BUF4VALID_SYSCLK12BUF4VALID_MASK 0xFF #define AB9540_SYSCLK12BUF4VALID_SYSCLK12BUF4VALID_MASK 0xFF
#define AB9540_SYSCLK12BUF4VALID_SYSCLK12BUF4VALID_SHIFT 0 #define AB9540_SYSCLK12BUF4VALID_SYSCLK12BUF4VALID_SHIFT 0
#define AB8500_ENABLE_WD 0x1
#define AB8500_KICK_WD 0x2
#define AB8500_WD_RESTART_ON_EXPIRE 0x10
#endif /* __AB8500_SYSCTRL_H */ #endif /* __AB8500_SYSCTRL_H */

View File

@ -362,6 +362,7 @@ struct ab8500 {
u8 *oldmask; u8 *oldmask;
int mask_size; int mask_size;
const int *irq_reg_offset; const int *irq_reg_offset;
int it_latchhier_num;
}; };
struct regulator_reg_init; struct regulator_reg_init;
@ -512,6 +513,8 @@ static inline int is_ab9540_2p0_or_earlier(struct ab8500 *ab)
return (is_ab9540(ab) && (ab->chip_id < AB8500_CUT2P0)); return (is_ab9540(ab) && (ab->chip_id < AB8500_CUT2P0));
} }
void ab8500_override_turn_on_stat(u8 mask, u8 set);
#ifdef CONFIG_AB8500_DEBUG #ifdef CONFIG_AB8500_DEBUG
void ab8500_dump_all_banks(struct device *dev); void ab8500_dump_all_banks(struct device *dev);
void ab8500_debug_register_interrupt(int line); void ab8500_debug_register_interrupt(int line);

View File

@ -17,8 +17,11 @@ struct ux500_charger;
struct ux500_charger_ops { struct ux500_charger_ops {
int (*enable) (struct ux500_charger *, int, int, int); int (*enable) (struct ux500_charger *, int, int, int);
int (*check_enable) (struct ux500_charger *, int, int);
int (*kick_wd) (struct ux500_charger *); int (*kick_wd) (struct ux500_charger *);
int (*update_curr) (struct ux500_charger *, int); int (*update_curr) (struct ux500_charger *, int);
int (*pp_enable) (struct ux500_charger *, bool);
int (*pre_chg_enable) (struct ux500_charger *, bool);
}; };
/** /**
@ -29,6 +32,7 @@ struct ux500_charger_ops {
* @max_out_curr maximum output charger current in mA * @max_out_curr maximum output charger current in mA
* @enabled indicates if this charger is used or not * @enabled indicates if this charger is used or not
* @external external charger unit (pm2xxx) * @external external charger unit (pm2xxx)
* @power_path USB power path support
*/ */
struct ux500_charger { struct ux500_charger {
struct power_supply psy; struct power_supply psy;
@ -38,6 +42,9 @@ struct ux500_charger {
int wdt_refresh; int wdt_refresh;
bool enabled; bool enabled;
bool external; bool external;
bool power_path;
}; };
extern struct blocking_notifier_head charger_notifier_list;
#endif #endif

View File

@ -48,7 +48,7 @@ struct pm2xxx_charger_platform_data {
size_t num_supplicants; size_t num_supplicants;
int i2c_bus; int i2c_bus;
const char *label; const char *label;
int irq_number; int gpio_irq_number;
unsigned int lpn_gpio; unsigned int lpn_gpio;
int irq_type; int irq_type;
}; };