summaryrefslogtreecommitdiff
path: root/driver/mp2964.c
blob: 21a23a8f4ce4ee6253200d5f65f44a8ac8377fb4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/* 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;
}