summaryrefslogtreecommitdiffstats
path: root/src/drivers/i2c/pi608gp/pi608gp.c
blob: 62d462eec89b53a37b21b99b4bdb7f41f2544a37 (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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* SPDX-License-Identifier: GPL-2.0-only */

#include <commonlib/endian.h>
#include <console/console.h>
#include <device/device.h>
#include <device/smbus.h>

#include "pi608gp.h"
#include "chip.h"

#define PI608GP_CMD_BLK_RD_INIT	0xba
#define PI608GP_CMD_BLK_RD	0xbd
#define PI608GP_CMD_BLK_WR	0xbe
#define PI608GP_SUBCMD_RD	0x04
#define PI608GP_SUBCMD_WR	0x03

/*
 * Only some of the available registers are implemented.
 * For a full list, see the PI7C9X2G608GP datasheet.
 */
#define PI608GP_REG_SW_OPMODE	0x74
#define PI608GP_REG_PHY_PAR1	0x78
#define PI608GP_REG_TL_CSR	0x8c

#define PI608GP_EN_4B		0x0f	/* Enable all 4 data bytes in SMBus messages. */
#define PI608GP_ENCODE_ERR	0xff

static uint8_t pi608gp_encode_amp_lvl(uint32_t level_mv)
{
	/* Allowed drive amplitude levels are in units of mV in range 0 to 475 mV with 25 mV
	   steps, based on Table 6-6 from the PI7C9X2G608GP datasheet. */
	if (level_mv > 475) {
		printk(BIOS_ERR, "PI608GP: Drive level %u mV out of range 0 to 475 mV!",
		       level_mv);
		return PI608GP_ENCODE_ERR;
	}
	if (level_mv % 25 != 0) {
		printk(BIOS_ERR, "PI608GP: Drive level %u mV not a multiple of 25!\n",
		       level_mv);
		return PI608GP_ENCODE_ERR;
	}

	/* The encoded value is a 5-bit number representing 25 mV steps. */
	return (level_mv / 25) & 0x1f;
}

static uint8_t pi608gp_encode_deemph_lvl(struct deemph_lvl level_mv)
{
	/* Table of allowed fixed-point millivolt values, based on Table 6-8 from the
	   PI7C9X2G608GP datasheet. */
	const struct deemph_lvl allowed[] = {
		{  0, 0}, {  6, 0}, { 12, 5}, { 19, 0}, { 25, 0}, { 31, 0}, { 37, 5}, { 44, 0},
		{ 50, 0}, { 56, 0}, { 62, 5}, { 69, 0}, { 75, 0}, { 81, 0}, { 87, 0}, { 94, 0},
		{100, 0}, {106, 0}, {112, 5}, {119, 0}, {125, 0}, {131, 0}, {137, 5}, {144, 0},
		{150, 0}, {156, 0}, {162, 5}, {169, 0}, {175, 0}, {181, 0}, {187, 5}, {194, 0},
	};

	for (int i = 0; i < ARRAY_SIZE(allowed); i++) {
		if (allowed[i].lvl == level_mv.lvl && allowed[i].lvl_10 == level_mv.lvl_10)
			/* When found, the encoded value is a 5-bit number that corresponds to
			   the index in the table of allowed values above. */
			return i & 0x1f;
	}

	printk(BIOS_ERR, "PI608GP: Requested unsupported de-emphasis level value: %u.%u mV!\n",
			level_mv.lvl, level_mv.lvl_10);
	return PI608GP_ENCODE_ERR;
}

static enum cb_err
pi608gp_reg_read(struct device *dev, uint8_t port, uint32_t reg_addr, uint32_t *val)
{
	int ret;

	/*
	 * Compose the SMBus message for register read init operation (from MSB to LSB):
	 * Byte 1: 7:3 = Rsvd., 2:0 = Command,
	 * Byte 2: 7:4 = Rsvd., 3:0 = Port Select[4:1],
	 * Byte 3: 7 = Port Select[0], 6 = Rsvd., 5:2 = Byte Enable, 1:0 = Reg. Addr. [11:10],
	 * Byte 4: 7:0 = Reg. Addr.[9:2] (Reg. Addr. [1:0] is fixed to 0).
	 */
	uint8_t buf[4] = {
		PI608GP_SUBCMD_RD,
		(port >> 1) & 0xf,
		((port & 0x1) << 7) | (PI608GP_EN_4B << 2) | ((reg_addr >> 10) & 0x3),
		(reg_addr >> 2) & 0xff,
	};

	/* Initialize register read operation */
	ret = smbus_block_write(dev, PI608GP_CMD_BLK_RD_INIT, sizeof(buf), buf);
	if (ret != sizeof(buf)) {
		printk(BIOS_ERR, "PI608GP: Unable to initiate register read!\n");
		return CB_ERR;
	}

	/* Perform the register read */
	ret = smbus_block_read(dev, PI608GP_CMD_BLK_RD, sizeof(buf), buf);
	if (ret != sizeof(buf)) {
		printk(BIOS_ERR, "PI608GP: Error reading register 0x%x (port %d)\n",
				reg_addr, port);
		return CB_ERR;
	}

	/* Retrieve back the value from the received SMBus packet in big endian order. */
	*val = read_be32((void *)buf);

	return CB_SUCCESS;
}

static enum cb_err
pi608gp_reg_write(struct device *dev, uint8_t port, uint32_t reg_addr, uint32_t val)
{
	int ret;

	/* Assemble register write command header, the same way as with read but add extra 4
	   bytes for the value. */
	uint8_t buf[8] = {
		PI608GP_SUBCMD_WR,
		(port >> 1) & 0xf,
		((port & 0x1) << 7) | (PI608GP_EN_4B << 2) | ((reg_addr >> 10) & 0x3),
		(reg_addr >> 2) & 0xff,
	};

	/* Insert register value to write in BE order after the header. */
	write_be32((void *)&buf[4], val);

	/* Perform the register write */
	ret = smbus_block_write(dev, PI608GP_CMD_BLK_WR, sizeof(buf), buf);
	if (ret != sizeof(buf)) {
		printk(BIOS_ERR, "PI608GP: Unable to write register 0x%x\n", reg_addr);
		return CB_ERR;
	}

	return CB_SUCCESS;
}

static enum cb_err
pi608gp_reg_update(struct device *dev, uint8_t port, uint32_t reg_addr, uint32_t and_mask,
		   uint32_t or_mask)
{
	uint32_t val;

	if (pi608gp_reg_read(dev, port, reg_addr, &val))
		return CB_ERR;

	val &= and_mask;
	val |= or_mask;

	if (pi608gp_reg_write(dev, port, reg_addr, val))
		return CB_ERR;

	return CB_SUCCESS;
}

static void pi608gp_init(struct device *dev)
{
	const uint8_t port = 0; /* Only the upstream port is being configured */
	struct drivers_i2c_pi608gp_config *config = dev->chip_info;
	uint8_t amp_lvl, deemph_lvl;

	/* The register values need to be encoded in a more complex way for the hardware. */
	amp_lvl = pi608gp_encode_amp_lvl(config->gen2_3p5_amp);
	deemph_lvl = pi608gp_encode_deemph_lvl(config->gen2_3p5_deemph);

	/* When the de-emphasis option isn't enabled or the values incorrectly encoded,
	   don't do anything. */
	if (!config->gen2_3p5_enable || amp_lvl == PI608GP_ENCODE_ERR ||
	    deemph_lvl == PI608GP_ENCODE_ERR)
		return;

	/* Enable -3,5 dB de-emphasis option (P35_GEN2_MODE). */
	if (pi608gp_reg_update(dev, port, PI608GP_REG_TL_CSR, ~0, 1 << 31))
		return;

	/* Set drive amplitude level for -3,5 dB de-emphasis (bits 20:16). */
	if (pi608gp_reg_update(dev, port, PI608GP_REG_SW_OPMODE, ~(0x1f << 16), amp_lvl << 16))
		return;

	/* Set drive de-emphasis for -3,5 dB on Gen 2 (bits 25:21). */
	if (pi608gp_reg_update(dev, port, PI608GP_REG_PHY_PAR1, ~(0x1f << 21), deemph_lvl << 21))
		return;
}

struct device_operations pi608gp_ops = {
	.read_resources		= noop_read_resources,
	.set_resources		= noop_set_resources,
	.init			= pi608gp_init,
};

struct chip_operations drivers_i2c_pi608gp_ops = {
	.name = "PI7C9X2G608GP",
};