/* Copyright 2021 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* Driver for tuning the MP2964 IMVP8 - IMVP9.1 parameters */ #include "console.h" #include "i2c.h" #include "mp2964.h" #include "timer.h" #include "util.h" #define MP2964_STARTUP_WAIT_US (50 * MSEC) #define MP2964_STORE_WAIT_US (300 * MSEC) #define MP2964_RESTORE_WAIT_US (2 * MSEC) enum reg_page { REG_PAGE_0, REG_PAGE_1, REG_PAGE_COUNT }; static int mp2964_write8(uint8_t reg, uint8_t value) { const uint8_t tx[2] = { reg, value }; return i2c_xfer_unlocked(I2C_PORT_MP2964, I2C_ADDR_MP2964_FLAGS, tx, sizeof(tx), NULL, 0, I2C_XFER_SINGLE); } static void mp2964_read16(uint8_t reg, uint16_t *value) { const uint8_t tx[1] = { reg }; uint8_t rx[2]; i2c_xfer_unlocked(I2C_PORT_MP2964, I2C_ADDR_MP2964_FLAGS, tx, sizeof(tx), rx, sizeof(rx), I2C_XFER_SINGLE); *value = (rx[1] << 8) | rx[0]; } static void mp2964_write16(uint8_t reg, uint16_t value) { const uint8_t tx[3] = { reg, value & 0xff, value >> 8 }; i2c_xfer_unlocked(I2C_PORT_MP2964, I2C_ADDR_MP2964_FLAGS, tx, sizeof(tx), NULL, 0, I2C_XFER_SINGLE); } static int mp2964_select_page(enum reg_page page) { int status; if (page >= REG_PAGE_COUNT) return EC_ERROR_INVAL; status = mp2964_write8(MP2964_PAGE, page); if (status != EC_SUCCESS) { ccprintf("%s: could not select page 0x%02x, error %d\n", __func__, page, status); } return status; } static void mp2964_write_vec16(const struct mp2964_reg_val *init_list, int count, int *delta) { const struct mp2964_reg_val *reg_val; uint16_t outval; int i; reg_val = init_list; for (i = 0; i < count; ++i, ++reg_val) { mp2964_read16(reg_val->reg, &outval); if (outval == reg_val->val) { ccprintf("mp2964: reg 0x%02x already 0x%04x\n", reg_val->reg, outval); continue; } ccprintf("mp2964: tuning reg 0x%02x from 0x%04x to 0x%04x\n", reg_val->reg, outval, reg_val->val); mp2964_write16(reg_val->reg, reg_val->val); *delta += 1; } } static int mp2964_store_user_all(void) { const uint8_t wr = MP2964_STORE_USER_ALL; const uint8_t rd = MP2964_RESTORE_USER_ALL; int status; ccprintf("%s: updating persistent settings\n", __func__); status = i2c_xfer_unlocked(I2C_PORT_MP2964, I2C_ADDR_MP2964_FLAGS, &wr, sizeof(wr), NULL, 0, I2C_XFER_SINGLE); if (status != EC_SUCCESS) return status; usleep(MP2964_STORE_WAIT_US); status = i2c_xfer_unlocked(I2C_PORT_MP2964, I2C_ADDR_MP2964_FLAGS, &rd, sizeof(rd), NULL, 0, I2C_XFER_SINGLE); if (status != EC_SUCCESS) return status; usleep(MP2964_RESTORE_WAIT_US); return EC_SUCCESS; } static void mp2964_patch_rail(enum reg_page page, const struct mp2964_reg_val *page_vals, int count, int *delta) { if (mp2964_select_page(page) != EC_SUCCESS) return; mp2964_write_vec16(page_vals, count, delta); } int mp2964_tune(const struct mp2964_reg_val *rail_a, int count_a, const struct mp2964_reg_val *rail_b, int count_b) { int tries = 2; int delta; udelay(MP2964_STARTUP_WAIT_US); i2c_lock(I2C_PORT_MP2964, 1); do { int status; delta = 0; mp2964_patch_rail(REG_PAGE_0, rail_a, count_a, &delta); mp2964_patch_rail(REG_PAGE_1, rail_b, count_b, &delta); if (delta == 0) break; status = mp2964_store_user_all(); if (status != EC_SUCCESS) ccprintf("%s: STORE_USER_ALL failed\n", __func__); } while (--tries > 0); i2c_lock(I2C_PORT_MP2964, 0); if (delta) return EC_ERROR_UNKNOWN; else return EC_SUCCESS; }