++static struct n810bm_adc_calib * n810bm_get_adc_calib(struct n810bm *bm,
++ enum n810bm_pmm_adc_id id)
++{
++ unsigned int index = 0;
++ struct n810bm_adc_calib *cal;
++
++ if (id != N810BM_PMM_ADC_0xFE)
++ index = (unsigned int)id + 1;
++ if (index >= ARRAY_SIZE(bm->calib.adc))
++ return NULL;
++
++ cal = &bm->calib.adc[index];
++ WARN_ON(cal->id && cal->id != id);
++
++ return cal;
++}
++
++static int pmm_record_get(struct n810bm *bm,
++ const struct firmware *pmm_block,
++ void *buffer, size_t length,
++ unsigned int group, unsigned int element, unsigned int offset)
++{
++ const u8 *pmm_area = pmm_block->data;
++ u8 active_group_mask;
++
++ if (pmm_block->size != N810BM_PMM_BLOCK_SIZE)
++ return -EINVAL;
++ if (group >= N810BM_PMM_BLOCK_SIZE / N810BM_PMM_GROUP_SIZE)
++ return -EINVAL;
++ if (element >= N810BM_PMM_GROUP_SIZE / N810BM_PMM_ELEM_SIZE)
++ return -EINVAL;
++ if (offset >= N810BM_PMM_ELEM_SIZE || length > N810BM_PMM_ELEM_SIZE ||
++ length + offset > N810BM_PMM_ELEM_SIZE)
++ return -EINVAL;
++
++ active_group_mask = pmm_area[16];
++ if (!(active_group_mask & (1 << group))) {
++ dev_dbg(&bm->pdev->dev, "pwm_record_get: Requested group %u, "
++ "but group is not active", group);
++ return -ENOENT;
++ }
++
++ memcpy(buffer,
++ pmm_area + group * N810BM_PMM_GROUP_SIZE
++ + element * N810BM_PMM_ELEM_SIZE
++ + offset,
++ length);
++
++ return 0;
++}
++
++/* PMM block group 1 element */
++struct group1_element {
++ u8 id;
++ u8 flags;
++ u8 adc_groupnr;
++ u8 _padding;
++ __le32 field1;
++ __le32 field2;
++} __packed;
++
++static int extract_group1_elem(struct n810bm *bm,
++ const struct firmware *pmm_block,
++ const enum n810bm_pmm_adc_id *pmm_adc_ids, size_t nr_pmm_adc_ids,
++ u32 field1_mask, u32 field2_mask)
++{
++ struct group1_element elem;
++ int err;
++ unsigned int i, element_nr;
++ struct n810bm_adc_calib *adc_calib;
++
++ for (i = 0; i < nr_pmm_adc_ids; i++) {
++ element_nr = (unsigned int)(pmm_adc_ids[i]) + 3;
++
++ err = pmm_record_get(bm, pmm_block, &elem, sizeof(elem),
++ 1, element_nr, 0);
++ if (err)
++ continue;
++ adc_calib = n810bm_get_adc_calib(bm, elem.id);
++ if (!adc_calib) {
++ dev_err(&bm->pdev->dev, "extract_group1_elem: "
++ "Could not get calib element for 0x%02X",
++ elem.id);
++ return -EINVAL;
++ }
++
++ if (adc_calib->flags == elem.flags) {
++ adc_calib->field1 = le32_to_cpu(elem.field1) & field1_mask;
++ adc_calib->field2 = le32_to_cpu(elem.field2) & field2_mask;
++ } else {
++ dev_dbg(&bm->pdev->dev, "extract_group1_elem: "
++ "Not extracting fields due to flags mismatch: "
++ "0x%02X vs 0x%02X",
++ adc_calib->flags, elem.flags);
++ }
++ }
++
++ return 0;
++}
++
++static int n810bm_parse_pmm_group1(struct n810bm *bm,
++ const struct firmware *pmm_block)
++{
++ struct n810bm_adc_calib *adc_calib;
++ struct group1_element elem;
++ int err;
++
++ static const enum n810bm_pmm_adc_id pmm_adc_ids_1[] = {
++ N810BM_PMM_ADC_BATVOLT,
++ N810BM_PMM_ADC_CHGVOLT,
++ N810BM_PMM_ADC_BKUPVOLT,
++ N810BM_PMM_ADC_BATCURR,
++ };
++ static const enum n810bm_pmm_adc_id pmm_adc_ids_2[] = {
++ N810BM_PMM_ADC_BSI,
++ };
++ static const enum n810bm_pmm_adc_id pmm_adc_ids_3[] = {
++ N810BM_PMM_ADC_BATTEMP,
++ };
++
++ /* Parse element 2 */
++ err = pmm_record_get(bm, pmm_block, &elem, sizeof(elem),
++ 1, 2, 0);
++ if (err) {
++ dev_err(&bm->pdev->dev,
++ "PMM: Failed to get group 1 / element 2");
++ return err;
++ }
++ if (elem.id == N810BM_PMM_ADC_0xFE && elem.flags == 0x05) {
++ adc_calib = n810bm_get_adc_calib(bm, elem.id);
++ if (!adc_calib) {
++ dev_err(&bm->pdev->dev,
++ "calib extract: Failed to get 0xFE calib");
++ return -EINVAL;
++ }
++ adc_calib->id = elem.id;
++ adc_calib->flags = elem.flags;
++ adc_calib->field1 = le32_to_cpu(elem.field1);
++ adc_calib->field2 = le32_to_cpu(elem.field2);
++ }
++
++ err = extract_group1_elem(bm, pmm_block,
++ pmm_adc_ids_1, ARRAY_SIZE(pmm_adc_ids_1),
++ 0xFFFFFFFF, 0xFFFFFFFF);
++ if (err)
++ return err;
++ err = extract_group1_elem(bm, pmm_block,
++ pmm_adc_ids_2, ARRAY_SIZE(pmm_adc_ids_2),
++ 0xFFFFFFFF, 0);
++ if (err)
++ return err;
++ err = extract_group1_elem(bm, pmm_block,
++ pmm_adc_ids_3, ARRAY_SIZE(pmm_adc_ids_3),
++ 0xFFFFFFFF, 0x0000FFFF);
++ if (err)
++ return err;
++
++ return 0;
++}
++
++static int n810bm_parse_pmm_group2(struct n810bm *bm,
++ const struct firmware *pmm_block)
++{
++ dev_err(&bm->pdev->dev, "TODO: CAL BME PMM group 2 parser not implemented, yet");
++ return -EOPNOTSUPP;
++}
++
++static void n810bm_adc_calib_set_defaults(struct n810bm *bm)
++{
++ struct n810bm_adc_calib *adc_calib;
++ unsigned int i;
++
++ static const struct n810bm_adc_calib defaults[] = {
++ /* ADC group-nr 0 */
++ {
++ .id = N810BM_PMM_ADC_HEADSET,
++ .flags = 0x00,
++ .adc_groupnr = 0,
++ }, {
++ .id = N810BM_PMM_ADC_HOOKDET,
++ .flags = 0x00,
++ .adc_groupnr = 0,
++ }, {
++ .id = N810BM_PMM_ADC_RFGP,
++ .flags = 0x00,
++ .adc_groupnr = 0,
++ }, {
++ .id = N810BM_PMM_ADC_LIGHTSENS,
++ .flags = 0x00,
++ .adc_groupnr = 0,
++ }, {
++ .id = N810BM_PMM_ADC_WBTX,
++ .flags = 0x00,
++ .adc_groupnr = 0,
++ }, {
++ .id = N810BM_PMM_ADC_RETUTEMP,
++ .flags = 0x00,
++ .adc_groupnr = 0,
++ }, {
++ .id = N810BM_PMM_ADC_GND2,
++ .flags = 0x00,
++ .adc_groupnr = 0,
++ },
++ /* ADC group-nr 1 */
++ {
++ .id = N810BM_PMM_ADC_0xFE,
++ .flags = 0x05,
++ .adc_groupnr = 1,
++ .field1 = (u32)-2,
++ .field2 = 13189,
++ }, {
++ .id = N810BM_PMM_ADC_BATVOLT,
++ .flags = 0x01,
++ .adc_groupnr = 1,
++ .field1 = 2527,
++ .field2 = 21373,
++ }, {
++ .id = N810BM_PMM_ADC_CHGVOLT,
++ .flags = 0x01,
++ .adc_groupnr = 1,
++ .field1 = 0,
++ .field2 = 129848,
++ }, {
++ .id = N810BM_PMM_ADC_BKUPVOLT,
++ .flags = 0x01,
++ .adc_groupnr = 1,
++ .field1 = 0,
++ .field2 = 20000,
++ }, {
++ .id = N810BM_PMM_ADC_BATCURR,
++ .flags = 0x06,
++ .adc_groupnr = 1,
++ .field1 = 0,
++ .field2 = 9660,
++ },
++ /* ADC group-nr 2 */
++ {
++ .id = N810BM_PMM_ADC_BSI,
++ .flags = 0x02,
++ .adc_groupnr = 2,
++ .field1 = 1169,
++ .field2 = 0,
++ },
++ /* ADC group-nr 3 */
++ {
++ .id = N810BM_PMM_ADC_BATTEMP,
++ .flags = 0x03,
++ .adc_groupnr = 3,
++ .field1 = 265423000,
++ .field2 = 298,
++ },
++ /* ADC group-nr 4 */
++ {
++ .id = N810BM_PMM_ADC_LIGHTTEMP,
++ .flags = 0x04,
++ .adc_groupnr = 4,
++ .field1 = 19533778,
++ .field2 = 308019670,
++ .field3 = 4700,
++ .field4 = 2500,
++ },
++ };
++
++ /* Clear the array */
++ memset(&bm->calib.adc, 0, sizeof(bm->calib.adc));
++ for (i = 0; i < ARRAY_SIZE(bm->calib.adc); i++)
++ bm->calib.adc[i].flags = 0xFF;
++
++ /* Copy the defaults */
++ for (i = 0; i < ARRAY_SIZE(defaults); i++) {
++ adc_calib = n810bm_get_adc_calib(bm, defaults[i].id);
++ if (WARN_ON(!adc_calib))
++ continue;
++ *adc_calib = defaults[i];
++ }
++}
++
++static int n810bm_parse_pmm_block(struct n810bm *bm,
++ const struct firmware *pmm_block)
++{
++ u8 byte;
++ int err;
++ unsigned int i, count;
++ struct n810bm_adc_calib *adc_calib;
++
++ /* Initialize to defaults */
++ n810bm_adc_calib_set_defaults(bm);
++
++ /* Parse the PMM data */
++ err = pmm_record_get(bm, pmm_block, &byte, sizeof(byte),
++ 1, 0, 0); /* group 1 / element 0 */
++ err |= (byte != 0x01);
++ err |= pmm_record_get(bm, pmm_block, &byte, sizeof(byte),
++ 1, 1, 0); /* group 1 / element 1 */
++ err |= (byte != 0x01);
++ if (err)
++ err = n810bm_parse_pmm_group2(bm, pmm_block);
++ else
++ err = n810bm_parse_pmm_group1(bm, pmm_block);
++ if (err)
++ return err;
++
++ /* Sanity checks */
++ for (i = 0, count = 0; i < ARRAY_SIZE(bm->calib.adc); i++) {
++ adc_calib = &bm->calib.adc[i];
++ if (adc_calib->flags == 0xFF)
++ continue;
++ switch (adc_calib->id) {
++ case N810BM_PMM_ADC_BATVOLT:
++ if (adc_calib->field1 < 2400 ||
++ adc_calib->field1 > 2700)
++ goto value_check_fail;
++ if (adc_calib->field2 < 20000 ||
++ adc_calib->field2 > 23000)
++ goto value_check_fail;
++ count++;
++ break;
++ case N810BM_PMM_ADC_BSI:
++ if (adc_calib->field1 < 1100 ||
++ adc_calib->field1 > 1300)
++ goto value_check_fail;
++ count++;
++ break;
++ case N810BM_PMM_ADC_BATCURR:
++ if (adc_calib->field2 < 7000 ||
++ adc_calib->field2 > 12000)
++ goto value_check_fail;
++ count++;
++ break;
++ case N810BM_PMM_ADC_0xFE:
++ if ((s32)adc_calib->field1 > 14 ||
++ (s32)adc_calib->field1 < -14)
++ goto value_check_fail;
++ if (adc_calib->field2 < 13000 ||
++ adc_calib->field2 > 13350)
++ goto value_check_fail;
++ count++;
++ break;
++ case N810BM_PMM_ADC_CHGVOLT:
++ case N810BM_PMM_ADC_BATTEMP:
++ case N810BM_PMM_ADC_BKUPVOLT:
++ count++;
++ break;
++ case N810BM_PMM_ADC_GND2:
++ case N810BM_PMM_ADC_HOOKDET:
++ case N810BM_PMM_ADC_LIGHTSENS:
++ case N810BM_PMM_ADC_HEADSET:
++ case N810BM_PMM_ADC_LIGHTTEMP:
++ case N810BM_PMM_ADC_RFGP:
++ case N810BM_PMM_ADC_WBTX:
++ case N810BM_PMM_ADC_RETUTEMP:
++ break;
++ }
++ dev_dbg(&bm->pdev->dev,
++ "ADC 0x%02X calib: 0x%02X 0x%02X 0x%08X 0x%08X 0x%04X 0x%04X",
++ adc_calib->id, adc_calib->flags, adc_calib->adc_groupnr,
++ adc_calib->field1, adc_calib->field2,
++ adc_calib->field3, adc_calib->field4);
++ }
++ if (count != 7) {
++ dev_err(&bm->pdev->dev, "PMM sanity check: Did not find "
++ "all required values (count=%u)", count);
++ goto check_fail;
++ }
++
++ return 0;
++
++value_check_fail:
++ dev_err(&bm->pdev->dev, "PMM image sanity check failed "
++ "(id=%02X, field1=%08X, field2=%08X)",
++ adc_calib->id, adc_calib->field1, adc_calib->field2);
++check_fail:
++ return -EILSEQ;
++}
++
++/* Set the current measure timer that triggers on Tahvo IRQ 7
++ * An interval of zero disables the timer. */
++static void n810bm_set_current_measure_timer(struct n810bm *bm,
++ u16 millisec_interval)
++{
++ u16 value = millisec_interval;
++
++ if (value <= 0xF905) {
++ value = ((u64)0x10624DD3 * (u64)(value + 0xF9)) >> 32;
++ value /= 16;
++ } else
++ value = 0xFF;
++
++ tahvo_write(bm, TAHVO_REG_BATCURRTIMER, value & 0xFF);
++
++ tahvo_set(bm, TAHVO_REG_CHGCTL,
++ TAHVO_REG_CHGCTL_CURTIMRST);
++ tahvo_clear(bm, TAHVO_REG_CHGCTL,
++ TAHVO_REG_CHGCTL_CURTIMRST);
++
++ if (millisec_interval)
++ tahvo_enable_irq(TAHVO_INT_BATCURR);
++ else
++ tahvo_disable_irq(TAHVO_INT_BATCURR);
++
++ //TODO also do a software timer for safety.
++}
++
++static void n810bm_enable_current_measure(struct n810bm *bm)
++{
++ WARN_ON(bm->current_measure_enabled < 0);
++ if (!bm->current_measure_enabled) {
++ /* Enable the current measurement circuitry */
++ tahvo_set(bm, TAHVO_REG_CHGCTL,
++ TAHVO_REG_CHGCTL_CURMEAS);
++ dev_dbg(&bm->pdev->dev,
++ "Current measurement circuitry enabled");
++ }
++ bm->current_measure_enabled++;
++}
++
++static void n810bm_disable_current_measure(struct n810bm *bm)
++{
++ bm->current_measure_enabled--;
++ WARN_ON(bm->current_measure_enabled < 0);
++ if (!bm->current_measure_enabled) {
++ /* Disable the current measurement circuitry */
++ tahvo_clear(bm, TAHVO_REG_CHGCTL,
++ TAHVO_REG_CHGCTL_CURMEAS);
++ dev_dbg(&bm->pdev->dev,
++ "Current measurement circuitry disabled");
++ }
++}
++
++/* Measure the actual battery current. Returns a signed value in mA.
++ * Does only work, if current measurement was enabled. */
++static int n810bm_measure_batt_current(struct n810bm *bm)
++{
++ u16 retval;
++ int adc = 0, ma, i;
++
++ if (WARN_ON(bm->current_measure_enabled <= 0))
++ return 0;
++ for (i = 0; i < 3; i++) {
++ retval = tahvo_read(bm, TAHVO_REG_BATCURR);
++ adc += (s16)retval; /* Value is signed */
++ }
++ adc /= 3;
++
++ //TODO convert to mA
++ ma = adc;
++
++ return ma;
++}
++
++/* Requires bm->mutex locked */
++static int n810bm_measure_batt_current_async(struct n810bm *bm)
++{
++ int ma;
++ bool charging = lipocharge_is_charging(&bm->charger);
++
++ n810bm_enable_current_measure(bm);
++ if (!charging)
++ WARN_ON(bm->active_current_pwm != 0);
++ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
++ TAHVO_REG_CHGCTL_EN |
++ TAHVO_REG_CHGCTL_PWMOVR |
++ TAHVO_REG_CHGCTL_PWMOVRZERO,
++ TAHVO_REG_CHGCTL_EN |
++ TAHVO_REG_CHGCTL_PWMOVR |
++ (charging ? 0 : TAHVO_REG_CHGCTL_PWMOVRZERO));
++ ma = n810bm_measure_batt_current(bm);
++ tahvo_maskset(bm, TAHVO_REG_CHGCTL,
++ TAHVO_REG_CHGCTL_EN |
++ TAHVO_REG_CHGCTL_PWMOVR |
++ TAHVO_REG_CHGCTL_PWMOVRZERO,
++ (charging ? TAHVO_REG_CHGCTL_EN : 0));
++ n810bm_disable_current_measure(bm);
++
++ return ma;
++}
++