blob: e50e7267b2eeb8fcc883ec04b848ac9205593dfa [file] [log] [blame]
Jan Sameke59f18b2023-04-12 14:36:02 +02001/* SPDX-License-Identifier: GPL-2.0-only */
2
3#include <commonlib/endian.h>
4#include <console/console.h>
5#include <device/device.h>
6#include <device/smbus.h>
7
8#include "pi608gp.h"
9#include "chip.h"
10
11#define PI608GP_CMD_BLK_RD_INIT 0xba
12#define PI608GP_CMD_BLK_RD 0xbd
13#define PI608GP_CMD_BLK_WR 0xbe
14#define PI608GP_SUBCMD_RD 0x04
15#define PI608GP_SUBCMD_WR 0x03
16
17/*
18 * Only some of the available registers are implemented.
19 * For a full list, see the PI7C9X2G608GP datasheet.
20 */
21#define PI608GP_REG_SW_OPMODE 0x74
22#define PI608GP_REG_PHY_PAR1 0x78
23#define PI608GP_REG_TL_CSR 0x8c
24
25#define PI608GP_EN_4B 0x0f /* Enable all 4 data bytes in SMBus messages. */
26#define PI608GP_ENCODE_ERR 0xff
27
28static uint8_t pi608gp_encode_amp_lvl(uint32_t level_mv)
29{
30 /* Allowed drive amplitude levels are in units of mV in range 0 to 475 mV with 25 mV
31 steps, based on Table 6-6 from the PI7C9X2G608GP datasheet. */
32 if (level_mv > 475) {
33 printk(BIOS_ERR, "PI608GP: Drive level %d mV out of range 0 to 475 mV!",
34 level_mv);
35 return PI608GP_ENCODE_ERR;
36 }
37 if (level_mv % 25 != 0) {
38 printk(BIOS_ERR, "PI608GP: Drive level %d mV not a multiple of 25!\n",
39 level_mv);
40 return PI608GP_ENCODE_ERR;
41 }
42
43 /* The encoded value is a 5-bit number representing 25 mV steps. */
44 return (level_mv / 25) & 0x1f;
45}
46
47static uint8_t pi608gp_encode_deemph_lvl(struct deemph_lvl level_mv)
48{
49 /* Table of allowed fixed-point millivolt values, based on Table 6-8 from the
50 PI7C9X2G608GP datasheet. */
51 struct deemph_lvl allowed[] = {
52 { 0, 0}, { 6, 0}, { 12, 5}, { 19, 0}, { 25, 0}, { 31, 0}, { 37, 5}, { 44, 0},
53 { 50, 0}, { 56, 0}, { 62, 5}, { 69, 0}, { 75, 0}, { 81, 0}, { 87, 0}, { 94, 0},
54 {100, 0}, {106, 0}, {112, 5}, {119, 0}, {125, 0}, {131, 0}, {137, 5}, {144, 0},
55 {150, 0}, {156, 0}, {162, 5}, {169, 0}, {175, 0}, {181, 0}, {187, 5}, {194, 0},
56 };
57
58 for (int i = 0; i < ARRAY_SIZE(allowed); i++) {
59 if (allowed[i].lvl == level_mv.lvl && allowed[i].lvl_10 == level_mv.lvl_10)
60 /* When found, the encoded value is a 5-bit number that corresponds to
61 the index in the table of allowed values above. */
62 return (uint8_t) (i & 0x1f);
63 }
64
65 printk(BIOS_ERR, "PI608GP: Requested unsupported de-emphasis level value: %d.%d mV!\n",
66 level_mv.lvl, level_mv.lvl_10);
67 return PI608GP_ENCODE_ERR;
68}
69
70static enum cb_err
71pi608gp_reg_read(struct device *dev, uint8_t port, uint32_t reg_addr, uint32_t *val)
72{
73 int ret;
74
75 /*
76 * Compose the SMBus message for register read init operation (from MSB to LSB):
77 * Byte 1: 7:3 = Rsvd., 2:0 = Command,
78 * Byte 2: 7:4 = Rsvd., 3:0 = Port Select[4:1],
79 * Byte 3: 7 = Port Select[0], 6 = Rsvd., 5:2 = Byte Enable, 1:0 = Reg. Addr. [11:10],
80 * Byte 4: 7:0 = Reg. Addr.[9:2] (Reg. Addr. [1:0] is fixed to 0).
81 */
82 uint8_t buf[4] = {
83 PI608GP_SUBCMD_RD,
84 (port >> 1) & 0xf,
85 ((port & 0x1) << 7) | (PI608GP_EN_4B << 2) | ((reg_addr >> 10) & 0x3),
86 (reg_addr >> 2) & 0xff,
87 };
88
89 /* Initialize register read operation */
90 ret = smbus_block_write(dev, PI608GP_CMD_BLK_RD_INIT, sizeof(buf), buf);
91 if (ret != sizeof(buf)) {
92 printk(BIOS_ERR, "PI608GP: Unable to initiate register read!\n");
93 return CB_ERR;
94 }
95
96 /* Perform the register read */
97 ret = smbus_block_read(dev, PI608GP_CMD_BLK_RD, sizeof(buf), buf);
98 if (ret != sizeof(buf)) {
99 printk(BIOS_ERR, "PI608GP: Error reading register 0x%x (port %d)\n",
100 reg_addr, port);
101 return CB_ERR;
102 }
103
104 /* Retrieve back the value from the received SMBus packet in big endian order. */
105 *val = read_be32((void *) buf);
106
107 return CB_SUCCESS;
108}
109
110static enum cb_err
111pi608gp_reg_write(struct device *dev, uint8_t port, uint32_t reg_addr, uint32_t val)
112{
113 int ret;
114
115 /* Assemble register write command header, the same way as with read but add extra 4
116 bytes for the value. */
117 uint8_t buf[8] = {
118 PI608GP_SUBCMD_WR,
119 (port >> 1) & 0xf,
120 ((port & 0x1) << 7) | (PI608GP_EN_4B << 2) | ((reg_addr >> 10) & 0x3),
121 (reg_addr >> 2) & 0xff,
122 };
123
124 /* Insert register value to write in BE order after the header. */
125 write_be32((void *) &buf[4], val);
126
127 /* Perform the register write */
128 ret = smbus_block_write(dev, PI608GP_CMD_BLK_WR, sizeof(buf), buf);
129 if (ret != sizeof(buf)) {
130 printk(BIOS_ERR, "PI608GP: Unable to write register 0x%x\n", reg_addr);
131 return CB_ERR;
132 }
133
134 return CB_SUCCESS;
135}
136
137static enum cb_err pi608gp_reg_update(struct device *dev, uint8_t port, uint32_t reg_addr,
138 uint32_t and_mask, uint32_t or_mask)
139{
140 uint32_t val;
141
142 if (pi608gp_reg_read(dev, port, reg_addr, &val))
143 return CB_ERR;
144
145 val &= and_mask;
146 val |= or_mask;
147
148 if (pi608gp_reg_write(dev, port, reg_addr, val))
149 return CB_ERR;
150
151 return CB_SUCCESS;
152}
153
154static void pi608gp_init(struct device *dev)
155{
156 const uint8_t port = 0; /* Only port 0 is being configured */
157 struct drivers_i2c_pi608gp_config *config = dev->chip_info;
158 uint8_t amp_lvl, deemph_lvl;
159
160 /* The register values need to be encoded in a more complex way for the hardware. */
161 amp_lvl = pi608gp_encode_amp_lvl(config->gen2_3p5_amp);
162 deemph_lvl = pi608gp_encode_deemph_lvl(config->gen2_3p5_deemph);
163
164 /* When the de-emphasis option isn't enabled or the values incorrectly encoded,
165 don't do anything. */
166 if (!config->gen2_3p5_enable || amp_lvl == PI608GP_ENCODE_ERR ||
167 deemph_lvl == PI608GP_ENCODE_ERR)
168 return;
169
170 /* Enable -3,5 dB de-emphasis option (P35_GEN2_MODE). */
171 if (pi608gp_reg_update(dev, port, PI608GP_REG_TL_CSR, ~0, 1 << 31))
172 return;
173
174 /* Set drive amplitude level for -3,5 dB de-emphasis (bits 20:16). */
175 if (pi608gp_reg_update(dev, port, PI608GP_REG_SW_OPMODE, ~(0x1f << 16), amp_lvl << 16))
176 return;
177
178 /* Set drive de-emphasis for -3,5 dB on Gen 2 (bits 25:21). */
179 if (pi608gp_reg_update(dev, port, PI608GP_REG_PHY_PAR1, ~(0x1f << 21), deemph_lvl << 21))
180 return;
181}
182
183struct device_operations pi608gp_ops = {
184 .read_resources = noop_read_resources,
185 .set_resources = noop_set_resources,
186 .init = pi608gp_init,
187};
188
189struct chip_operations drivers_i2c_pi608gp_ops = {
190 CHIP_NAME("PI7C9X2G608GP")
191};