1 /* SPDX-License-Identifier: GPL-2.0-only */
3 #include <commonlib/endian.h>
4 #include <console/console.h>
5 #include <device/device.h>
6 #include <device/smbus.h>
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
18 * Only some of the available registers are implemented.
19 * For a full list, see the PI7C9X2G608GP datasheet.
21 #define PI608GP_REG_SW_OPMODE 0x74
22 #define PI608GP_REG_PHY_PAR1 0x78
23 #define PI608GP_REG_TL_CSR 0x8c
25 #define PI608GP_EN_4B 0x0f /* Enable all 4 data bytes in SMBus messages. */
26 #define PI608GP_ENCODE_ERR 0xff
28 static uint8_t pi608gp_encode_amp_lvl(uint32_t level_mv
)
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. */
33 printk(BIOS_ERR
, "PI608GP: Drive level %u mV out of range 0 to 475 mV!",
35 return PI608GP_ENCODE_ERR
;
37 if (level_mv
% 25 != 0) {
38 printk(BIOS_ERR
, "PI608GP: Drive level %u mV not a multiple of 25!\n",
40 return PI608GP_ENCODE_ERR
;
43 /* The encoded value is a 5-bit number representing 25 mV steps. */
44 return (level_mv
/ 25) & 0x1f;
47 static uint8_t pi608gp_encode_deemph_lvl(struct deemph_lvl level_mv
)
49 /* Table of allowed fixed-point millivolt values, based on Table 6-8 from the
50 PI7C9X2G608GP datasheet. */
51 const 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},
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. */
65 printk(BIOS_ERR
, "PI608GP: Requested unsupported de-emphasis level value: %u.%u mV!\n",
66 level_mv
.lvl
, level_mv
.lvl_10
);
67 return PI608GP_ENCODE_ERR
;
71 pi608gp_reg_read(struct device
*dev
, uint8_t port
, uint32_t reg_addr
, uint32_t *val
)
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).
85 ((port
& 0x1) << 7) | (PI608GP_EN_4B
<< 2) | ((reg_addr
>> 10) & 0x3),
86 (reg_addr
>> 2) & 0xff,
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");
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",
104 /* Retrieve back the value from the received SMBus packet in big endian order. */
105 *val
= read_be32((void *)buf
);
111 pi608gp_reg_write(struct device
*dev
, uint8_t port
, uint32_t reg_addr
, uint32_t val
)
115 /* Assemble register write command header, the same way as with read but add extra 4
116 bytes for the value. */
120 ((port
& 0x1) << 7) | (PI608GP_EN_4B
<< 2) | ((reg_addr
>> 10) & 0x3),
121 (reg_addr
>> 2) & 0xff,
124 /* Insert register value to write in BE order after the header. */
125 write_be32((void *)&buf
[4], val
);
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
);
138 pi608gp_reg_update(struct device
*dev
, uint8_t port
, uint32_t reg_addr
, uint32_t and_mask
,
143 if (pi608gp_reg_read(dev
, port
, reg_addr
, &val
))
149 if (pi608gp_reg_write(dev
, port
, reg_addr
, val
))
155 static void pi608gp_init(struct device
*dev
)
157 const uint8_t port
= 0; /* Only the upstream port is being configured */
158 struct drivers_i2c_pi608gp_config
*config
= dev
->chip_info
;
159 uint8_t amp_lvl
, deemph_lvl
;
161 /* The register values need to be encoded in a more complex way for the hardware. */
162 amp_lvl
= pi608gp_encode_amp_lvl(config
->gen2_3p5_amp
);
163 deemph_lvl
= pi608gp_encode_deemph_lvl(config
->gen2_3p5_deemph
);
165 /* When the de-emphasis option isn't enabled or the values incorrectly encoded,
166 don't do anything. */
167 if (!config
->gen2_3p5_enable
|| amp_lvl
== PI608GP_ENCODE_ERR
||
168 deemph_lvl
== PI608GP_ENCODE_ERR
)
171 /* Enable -3,5 dB de-emphasis option (P35_GEN2_MODE). */
172 if (pi608gp_reg_update(dev
, port
, PI608GP_REG_TL_CSR
, ~0, 1 << 31))
175 /* Set drive amplitude level for -3,5 dB de-emphasis (bits 20:16). */
176 if (pi608gp_reg_update(dev
, port
, PI608GP_REG_SW_OPMODE
, ~(0x1f << 16), amp_lvl
<< 16))
179 /* Set drive de-emphasis for -3,5 dB on Gen 2 (bits 25:21). */
180 if (pi608gp_reg_update(dev
, port
, PI608GP_REG_PHY_PAR1
, ~(0x1f << 21), deemph_lvl
<< 21))
184 struct device_operations pi608gp_ops
= {
185 .read_resources
= noop_read_resources
,
186 .set_resources
= noop_set_resources
,
187 .init
= pi608gp_init
,
190 struct chip_operations drivers_i2c_pi608gp_ops
= {
191 .name
= "PI7C9X2G608GP",