// SPDX-License-Identifier: GPL-2.0+ /* * OnSemi NB7VPQ904M Type-C driver * * Copyright (C) 2023 Dmitry Baryshkov */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define NB7_CHNA 0 #define NB7_CHNB 1 #define NB7_CHNC 2 #define NB7_CHND 3 #define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND) #define GEN_DEV_SET_REG 0x00 #define GEN_DEV_SET_CHIP_EN BIT(0) #define GEN_DEV_SET_CHNA_EN BIT(4) #define GEN_DEV_SET_CHNB_EN BIT(5) #define GEN_DEV_SET_CHNC_EN BIT(6) #define GEN_DEV_SET_CHND_EN BIT(7) #define GEN_DEV_SET_OP_MODE_MASK GENMASK(3, 1) #define GEN_DEV_SET_OP_MODE_DP_CC2 0 #define GEN_DEV_SET_OP_MODE_DP_CC1 1 #define GEN_DEV_SET_OP_MODE_DP_4LANE 2 #define GEN_DEV_SET_OP_MODE_USB 5 #define EQ_SETTING_REG_BASE 0x01 #define EQ_SETTING_REG(n) (EQ_SETTING_REG_BASE + (n) * 2) #define EQ_SETTING_MASK GENMASK(3, 1) #define OUTPUT_COMPRESSION_AND_POL_REG_BASE 0x02 #define OUTPUT_COMPRESSION_AND_POL_REG(n) (OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2) #define OUTPUT_COMPRESSION_MASK GENMASK(2, 1) #define FLAT_GAIN_REG_BASE 0x18 #define FLAT_GAIN_REG(n) (FLAT_GAIN_REG_BASE + (n) * 2) #define FLAT_GAIN_MASK GENMASK(1, 0) #define LOSS_MATCH_REG_BASE 0x19 #define LOSS_MATCH_REG(n) (LOSS_MATCH_REG_BASE + (n) * 2) #define LOSS_MATCH_MASK GENMASK(1, 0) #define AUX_CC_REG 0x09 #define CHIP_VERSION_REG 0x17 struct nb7vpq904m { struct i2c_client *client; struct gpio_desc *enable_gpio; struct regulator *vcc_supply; struct regmap *regmap; struct typec_switch_dev *sw; struct typec_retimer *retimer; bool swap_data_lanes; struct typec_switch *typec_switch; struct drm_bridge bridge; struct mutex lock; /* protect non-concurrent retimer & switch */ enum typec_orientation orientation; unsigned long mode; unsigned int svid; }; static void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp) { u8 eq, out_comp, flat_gain, loss_match; if (dp) { eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4; out_comp = 0x3; flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1; loss_match = 0x3; } else { eq = 0x4; out_comp = 0x3; flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1; loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3; } regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel), EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq)); regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel), OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp)); regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel), FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain)); regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel), LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match)); } static int nb7vpq904m_set(struct nb7vpq904m *nb7) { bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE); switch (nb7->mode) { case TYPEC_STATE_SAFE: regmap_write(nb7->regmap, GEN_DEV_SET_REG, GEN_DEV_SET_CHIP_EN | GEN_DEV_SET_CHNA_EN | GEN_DEV_SET_CHNB_EN | GEN_DEV_SET_CHNC_EN | GEN_DEV_SET_CHND_EN | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, GEN_DEV_SET_OP_MODE_USB)); nb7vpq904m_set_channel(nb7, NB7_CHNA, false); nb7vpq904m_set_channel(nb7, NB7_CHNB, false); nb7vpq904m_set_channel(nb7, NB7_CHNC, false); nb7vpq904m_set_channel(nb7, NB7_CHND, false); regmap_write(nb7->regmap, AUX_CC_REG, 0x2); return 0; case TYPEC_STATE_USB: /* * Normal Orientation (CC1) * A -> USB RX * B -> USB TX * C -> X * D -> X * Flipped Orientation (CC2) * A -> X * B -> X * C -> USB TX * D -> USB RX * * Reversed if data lanes are swapped */ if (reverse ^ nb7->swap_data_lanes) { regmap_write(nb7->regmap, GEN_DEV_SET_REG, GEN_DEV_SET_CHIP_EN | GEN_DEV_SET_CHNA_EN | GEN_DEV_SET_CHNB_EN | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, GEN_DEV_SET_OP_MODE_USB)); nb7vpq904m_set_channel(nb7, NB7_CHNA, false); nb7vpq904m_set_channel(nb7, NB7_CHNB, false); } else { regmap_write(nb7->regmap, GEN_DEV_SET_REG, GEN_DEV_SET_CHIP_EN | GEN_DEV_SET_CHNC_EN | GEN_DEV_SET_CHND_EN | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, GEN_DEV_SET_OP_MODE_USB)); nb7vpq904m_set_channel(nb7, NB7_CHNC, false); nb7vpq904m_set_channel(nb7, NB7_CHND, false); } regmap_write(nb7->regmap, AUX_CC_REG, 0x2); return 0; default: if (nb7->svid != USB_TYPEC_DP_SID) return -EINVAL; break; } /* DP Altmode Setup */ regmap_write(nb7->regmap, AUX_CC_REG, reverse ? 0x1 : 0x0); switch (nb7->mode) { case TYPEC_DP_STATE_C: case TYPEC_DP_STATE_E: /* * Normal Orientation (CC1) * A -> DP3 * B -> DP2 * C -> DP1 * D -> DP0 * Flipped Orientation (CC2) * A -> DP0 * B -> DP1 * C -> DP2 * D -> DP3 */ regmap_write(nb7->regmap, GEN_DEV_SET_REG, GEN_DEV_SET_CHIP_EN | GEN_DEV_SET_CHNA_EN | GEN_DEV_SET_CHNB_EN | GEN_DEV_SET_CHNC_EN | GEN_DEV_SET_CHND_EN | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, GEN_DEV_SET_OP_MODE_DP_4LANE)); nb7vpq904m_set_channel(nb7, NB7_CHNA, true); nb7vpq904m_set_channel(nb7, NB7_CHNB, true); nb7vpq904m_set_channel(nb7, NB7_CHNC, true); nb7vpq904m_set_channel(nb7, NB7_CHND, true); break; case TYPEC_DP_STATE_D: case TYPEC_DP_STATE_F: regmap_write(nb7->regmap, GEN_DEV_SET_REG, GEN_DEV_SET_CHIP_EN | GEN_DEV_SET_CHNA_EN | GEN_DEV_SET_CHNB_EN | GEN_DEV_SET_CHNC_EN | GEN_DEV_SET_CHND_EN | FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, reverse ^ nb7->swap_data_lanes ? GEN_DEV_SET_OP_MODE_DP_CC2 : GEN_DEV_SET_OP_MODE_DP_CC1)); /* * Normal Orientation (CC1) * A -> USB RX * B -> USB TX * C -> DP1 * D -> DP0 * Flipped Orientation (CC2) * A -> DP0 * B -> DP1 * C -> USB TX * D -> USB RX * * Reversed if data lanes are swapped */ if (nb7->swap_data_lanes) { nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse); nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse); nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse); nb7vpq904m_set_channel(nb7, NB7_CHND, reverse); } else { nb7vpq904m_set_channel(nb7, NB7_CHNA, reverse); nb7vpq904m_set_channel(nb7, NB7_CHNB, reverse); nb7vpq904m_set_channel(nb7, NB7_CHNC, !reverse); nb7vpq904m_set_channel(nb7, NB7_CHND, !reverse); } break; default: return -EOPNOTSUPP; } return 0; } static int nb7vpq904m_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) { struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw); int ret; ret = typec_switch_set(nb7->typec_switch, orientation); if (ret) return ret; mutex_lock(&nb7->lock); if (nb7->orientation != orientation) { nb7->orientation = orientation; ret = nb7vpq904m_set(nb7); } mutex_unlock(&nb7->lock); return ret; } static int nb7vpq904m_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) { struct nb7vpq904m *nb7 = typec_retimer_get_drvdata(retimer); int ret = 0; mutex_lock(&nb7->lock); if (nb7->mode != state->mode) { nb7->mode = state->mode; if (state->alt) nb7->svid = state->alt->svid; else nb7->svid = 0; // No SVID ret = nb7vpq904m_set(nb7); } mutex_unlock(&nb7->lock); return ret; } #if IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL_BRIDGE) static int nb7vpq904m_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct nb7vpq904m *nb7 = container_of(bridge, struct nb7vpq904m, bridge); struct drm_bridge *next_bridge; if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) return -EINVAL; next_bridge = devm_drm_of_get_bridge(&nb7->client->dev, nb7->client->dev.of_node, 0, 0); if (IS_ERR(next_bridge)) { dev_err(&nb7->client->dev, "failed to acquire drm_bridge: %pe\n", next_bridge); return PTR_ERR(next_bridge); } return drm_bridge_attach(bridge->encoder, next_bridge, bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); } static const struct drm_bridge_funcs nb7vpq904m_bridge_funcs = { .attach = nb7vpq904m_bridge_attach, }; static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7) { nb7->bridge.funcs = &nb7vpq904m_bridge_funcs; nb7->bridge.of_node = nb7->client->dev.of_node; return devm_drm_bridge_add(&nb7->client->dev, &nb7->bridge); } #else static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7) { return 0; } #endif static const struct regmap_config nb7_regmap = { .max_register = 0x1f, .reg_bits = 8, .val_bits = 8, }; enum { NORMAL_LANE_MAPPING, INVERT_LANE_MAPPING, }; #define DATA_LANES_COUNT 4 static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = { [NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 }, [INVERT_LANE_MAPPING] = { 3, 2, 1, 0 }, }; static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7) { struct device_node *ep; u32 data_lanes[4]; int ret, i, j; ep = of_graph_get_endpoint_by_regs(nb7->client->dev.of_node, 1, 0); if (ep) { ret = of_property_count_u32_elems(ep, "data-lanes"); if (ret == -EINVAL) /* Property isn't here, consider default mapping */ goto out_done; if (ret < 0) goto out_error; if (ret != DATA_LANES_COUNT) { dev_err(&nb7->client->dev, "expected 4 data lanes\n"); ret = -EINVAL; goto out_error; } ret = of_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT); if (ret) goto out_error; for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) { for (j = 0; j < DATA_LANES_COUNT; j++) { if (data_lanes[j] != supported_data_lane_mapping[i][j]) break; } if (j == DATA_LANES_COUNT) break; } switch (i) { case NORMAL_LANE_MAPPING: break; case INVERT_LANE_MAPPING: nb7->swap_data_lanes = true; dev_info(&nb7->client->dev, "using inverted data lanes mapping\n"); break; default: dev_err(&nb7->client->dev, "invalid data lanes mapping\n"); ret = -EINVAL; goto out_error; } } out_done: ret = 0; out_error: of_node_put(ep); return ret; } static int nb7vpq904m_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct typec_switch_desc sw_desc = { }; struct typec_retimer_desc retimer_desc = { }; struct nb7vpq904m *nb7; int ret; nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL); if (!nb7) return -ENOMEM; nb7->client = client; nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap); if (IS_ERR(nb7->regmap)) { dev_err(&client->dev, "Failed to allocate register map\n"); return PTR_ERR(nb7->regmap); } nb7->mode = TYPEC_STATE_SAFE; nb7->orientation = TYPEC_ORIENTATION_NONE; mutex_init(&nb7->lock); nb7->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(nb7->enable_gpio)) return dev_err_probe(dev, PTR_ERR(nb7->enable_gpio), "unable to acquire enable gpio\n"); nb7->vcc_supply = devm_regulator_get_optional(dev, "vcc"); if (IS_ERR(nb7->vcc_supply)) return PTR_ERR(nb7->vcc_supply); nb7->typec_switch = fwnode_typec_switch_get(dev->fwnode); if (IS_ERR(nb7->typec_switch)) return dev_err_probe(dev, PTR_ERR(nb7->typec_switch), "failed to acquire orientation-switch\n"); ret = nb7vpq904m_parse_data_lanes_mapping(nb7); if (ret) return ret; ret = regulator_enable(nb7->vcc_supply); if (ret) dev_warn(dev, "Failed to enable vcc: %d\n", ret); gpiod_set_value(nb7->enable_gpio, 1); ret = nb7vpq904m_register_bridge(nb7); if (ret) return ret; sw_desc.drvdata = nb7; sw_desc.fwnode = dev->fwnode; sw_desc.set = nb7vpq904m_sw_set; nb7->sw = typec_switch_register(dev, &sw_desc); if (IS_ERR(nb7->sw)) return dev_err_probe(dev, PTR_ERR(nb7->sw), "Error registering typec switch\n"); retimer_desc.drvdata = nb7; retimer_desc.fwnode = dev->fwnode; retimer_desc.set = nb7vpq904m_retimer_set; nb7->retimer = typec_retimer_register(dev, &retimer_desc); if (IS_ERR(nb7->retimer)) { typec_switch_unregister(nb7->sw); return dev_err_probe(dev, PTR_ERR(nb7->retimer), "Error registering typec retimer\n"); } return 0; } static void nb7vpq904m_remove(struct i2c_client *client) { struct nb7vpq904m *nb7 = i2c_get_clientdata(client); typec_retimer_unregister(nb7->retimer); typec_switch_unregister(nb7->sw); gpiod_set_value(nb7->enable_gpio, 0); regulator_disable(nb7->vcc_supply); } static const struct i2c_device_id nb7vpq904m_table[] = { { "nb7vpq904m" }, { } }; MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table); static const struct of_device_id nb7vpq904m_of_table[] = { { .compatible = "onnn,nb7vpq904m" }, { } }; MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table); static struct i2c_driver nb7vpq904m_driver = { .driver = { .name = "nb7vpq904m", .of_match_table = nb7vpq904m_of_table, }, .probe_new = nb7vpq904m_probe, .remove = nb7vpq904m_remove, .id_table = nb7vpq904m_table, }; module_i2c_driver(nb7vpq904m_driver); MODULE_AUTHOR("Dmitry Baryshkov "); MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver"); MODULE_LICENSE("GPL");