summaryrefslogtreecommitdiff
path: root/src/drivers/i2c/pi608gp
diff options
context:
space:
mode:
authorJan Samek <jan.samek@siemens.com>2023-04-12 14:36:02 +0200
committerFelix Held <felix-coreboot@felixheld.de>2023-05-08 13:11:22 +0000
commite59f18bf29a8db4447100c57d3400ec432a94961 (patch)
tree341552fe9873700dbdb3f8b843fbd0a94192d36b /src/drivers/i2c/pi608gp
parent995772f0c3126672773983cb8e7a7d8f0b23b379 (diff)
drivers/i2c: Add PI7C9X2G608GP PCIe switch driver (pi608gp)
This patch adds some of the variety of configuration options exported by the Pericom Inc. PI7C9X2G608GP PCIe switch over its SMBus interface. Currently implemented options are only used to adjust the switch upstream port amplitude and de-emphasis levels in millivolts. Only values specified in the switch datasheet (in tables 6-6 and 6-8) are allowed. Example of a devicetree.cb entry: chip drivers/i2c/pi608gp register "gen2_3p5_enable" = "true" register "gen2_3p5_amp" = "AMP_LVL_MV(425)" register "gen2_3p5_deemph" = \ "DEEMPH_LVL_MV(37, 5)" device i2c 0x6f on ops pi608gp_ops end end Link to the datasheet: https://web.archive.org/web/20210225074853/https://www.diodes.com/assets/Datasheets/PI7C9X2G608GP.pdf BUG=none TEST=Create devicetree.cb and Kconfig entries for this driver in a mainboard containing the switch and verify, that the values read out from the switch config space match the values programmed over the SMBus. Change-Id: Id191c4e97b99da58efd3ba38bf8cca3603ece4d5 Signed-off-by: Jan Samek <jan.samek@siemens.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/74433 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Felix Held <felix-coreboot@felixheld.de> Reviewed-by: Mario Scheithauer <mario.scheithauer@siemens.com>
Diffstat (limited to 'src/drivers/i2c/pi608gp')
-rw-r--r--src/drivers/i2c/pi608gp/Kconfig5
-rw-r--r--src/drivers/i2c/pi608gp/Makefile.inc1
-rw-r--r--src/drivers/i2c/pi608gp/chip.h14
-rw-r--r--src/drivers/i2c/pi608gp/pi608gp.c191
-rw-r--r--src/drivers/i2c/pi608gp/pi608gp.h12
5 files changed, 223 insertions, 0 deletions
diff --git a/src/drivers/i2c/pi608gp/Kconfig b/src/drivers/i2c/pi608gp/Kconfig
new file mode 100644
index 0000000000..f0e5ba2e55
--- /dev/null
+++ b/src/drivers/i2c/pi608gp/Kconfig
@@ -0,0 +1,5 @@
+config DRIVERS_I2C_PI608GP
+ bool
+ default n
+ help
+ Enable support for configuring the Pericom PI7C9X2G608GP Gen2 PCIe switch over SMBus.
diff --git a/src/drivers/i2c/pi608gp/Makefile.inc b/src/drivers/i2c/pi608gp/Makefile.inc
new file mode 100644
index 0000000000..21516a6d46
--- /dev/null
+++ b/src/drivers/i2c/pi608gp/Makefile.inc
@@ -0,0 +1 @@
+ramstage-$(CONFIG_DRIVERS_I2C_PI608GP) += pi608gp.c
diff --git a/src/drivers/i2c/pi608gp/chip.h b/src/drivers/i2c/pi608gp/chip.h
new file mode 100644
index 0000000000..ca4a8c47ae
--- /dev/null
+++ b/src/drivers/i2c/pi608gp/chip.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __DRIVERS_I2C_PI608GP_CHIP_H__
+#define __DRIVERS_I2C_PI608GP_CHIP_H__
+
+#include "pi608gp.h"
+
+struct drivers_i2c_pi608gp_config {
+ bool gen2_3p5_enable;
+ uint32_t gen2_3p5_amp;
+ struct deemph_lvl gen2_3p5_deemph;
+};
+
+#endif /* __DRIVERS_I2C_PI608GP_CHIP_H__ */
diff --git a/src/drivers/i2c/pi608gp/pi608gp.c b/src/drivers/i2c/pi608gp/pi608gp.c
new file mode 100644
index 0000000000..e50e7267b2
--- /dev/null
+++ b/src/drivers/i2c/pi608gp/pi608gp.c
@@ -0,0 +1,191 @@
+/* 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 %d 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 %d 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. */
+ 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 (uint8_t) (i & 0x1f);
+ }
+
+ printk(BIOS_ERR, "PI608GP: Requested unsupported de-emphasis level value: %d.%d 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 port 0 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 = {
+ CHIP_NAME("PI7C9X2G608GP")
+};
diff --git a/src/drivers/i2c/pi608gp/pi608gp.h b/src/drivers/i2c/pi608gp/pi608gp.h
new file mode 100644
index 0000000000..fe2776be01
--- /dev/null
+++ b/src/drivers/i2c/pi608gp/pi608gp.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _I2C_PI608GP_H_
+#define _I2C_PI608GP_H_
+
+/* Struct to store fixed-point millivolt values */
+struct deemph_lvl { uint32_t lvl, lvl_10; };
+
+#define AMP_LVL_MV(_LVL) (_LVL)
+#define DEEMPH_LVL_MV(_LVL, _LVL_10) { .lvl = _LVL, .lvl_10 = _LVL_10 }
+
+#endif