// SPDX-License-Identifier: GPL-2.0 /* * Willsemi WUSB3801 Type-C port controller driver * * Copyright (C) 2022 Samuel Holland */ #include #include #include #include #include #define WUSB3801_REG_DEVICE_ID 0x01 #define WUSB3801_REG_CTRL0 0x02 #define WUSB3801_REG_INT 0x03 #define WUSB3801_REG_STAT 0x04 #define WUSB3801_REG_CTRL1 0x05 #define WUSB3801_REG_TEST00 0x06 #define WUSB3801_REG_TEST01 0x07 #define WUSB3801_REG_TEST02 0x08 #define WUSB3801_REG_TEST03 0x09 #define WUSB3801_REG_TEST04 0x0a #define WUSB3801_REG_TEST05 0x0b #define WUSB3801_REG_TEST06 0x0c #define WUSB3801_REG_TEST07 0x0d #define WUSB3801_REG_TEST08 0x0e #define WUSB3801_REG_TEST09 0x0f #define WUSB3801_REG_TEST0A 0x10 #define WUSB3801_REG_TEST0B 0x11 #define WUSB3801_REG_TEST0C 0x12 #define WUSB3801_REG_TEST0D 0x13 #define WUSB3801_REG_TEST0E 0x14 #define WUSB3801_REG_TEST0F 0x15 #define WUSB3801_REG_TEST10 0x16 #define WUSB3801_REG_TEST11 0x17 #define WUSB3801_REG_TEST12 0x18 #define WUSB3801_DEVICE_ID_VERSION_ID GENMASK(7, 3) #define WUSB3801_DEVICE_ID_VENDOR_ID GENMASK(2, 0) #define WUSB3801_CTRL0_DIS_ACC_SUPPORT BIT(7) #define WUSB3801_CTRL0_TRY GENMASK(6, 5) #define WUSB3801_CTRL0_TRY_NONE (0x0 << 5) #define WUSB3801_CTRL0_TRY_SNK (0x1 << 5) #define WUSB3801_CTRL0_TRY_SRC (0x2 << 5) #define WUSB3801_CTRL0_CURRENT GENMASK(4, 3) /* SRC */ #define WUSB3801_CTRL0_CURRENT_DEFAULT (0x0 << 3) #define WUSB3801_CTRL0_CURRENT_1_5A (0x1 << 3) #define WUSB3801_CTRL0_CURRENT_3_0A (0x2 << 3) #define WUSB3801_CTRL0_ROLE GENMASK(2, 1) #define WUSB3801_CTRL0_ROLE_SNK (0x0 << 1) #define WUSB3801_CTRL0_ROLE_SRC (0x1 << 1) #define WUSB3801_CTRL0_ROLE_DRP (0x2 << 1) #define WUSB3801_CTRL0_INT_MASK BIT(0) #define WUSB3801_INT_ATTACHED BIT(0) #define WUSB3801_INT_DETACHED BIT(1) #define WUSB3801_STAT_VBUS_DETECTED BIT(7) #define WUSB3801_STAT_CURRENT GENMASK(6, 5) /* SNK */ #define WUSB3801_STAT_CURRENT_STANDBY (0x0 << 5) #define WUSB3801_STAT_CURRENT_DEFAULT (0x1 << 5) #define WUSB3801_STAT_CURRENT_1_5A (0x2 << 5) #define WUSB3801_STAT_CURRENT_3_0A (0x3 << 5) #define WUSB3801_STAT_PARTNER GENMASK(4, 2) #define WUSB3801_STAT_PARTNER_STANDBY (0x0 << 2) #define WUSB3801_STAT_PARTNER_SNK (0x1 << 2) #define WUSB3801_STAT_PARTNER_SRC (0x2 << 2) #define WUSB3801_STAT_PARTNER_AUDIO (0x3 << 2) #define WUSB3801_STAT_PARTNER_DEBUG (0x4 << 2) #define WUSB3801_STAT_ORIENTATION GENMASK(1, 0) #define WUSB3801_STAT_ORIENTATION_NONE (0x0 << 0) #define WUSB3801_STAT_ORIENTATION_CC1 (0x1 << 0) #define WUSB3801_STAT_ORIENTATION_CC2 (0x2 << 0) #define WUSB3801_STAT_ORIENTATION_BOTH (0x3 << 0) #define WUSB3801_CTRL1_SM_RESET BIT(0) #define WUSB3801_TEST01_VENDOR_SUB_ID (BIT(8) | BIT(6)) #define WUSB3801_TEST02_FORCE_ERR_RCY BIT(8) #define WUSB3801_TEST0A_WAIT_VBUS BIT(5) struct wusb3801 { struct typec_capability cap; struct device *dev; struct typec_partner *partner; struct typec_port *port; struct regmap *regmap; struct regulator *vbus_supply; unsigned int partner_type; enum typec_port_type port_type; enum typec_pwr_opmode pwr_opmode; bool vbus_on; }; static enum typec_role wusb3801_get_default_role(struct wusb3801 *wusb3801) { switch (wusb3801->port_type) { case TYPEC_PORT_SRC: return TYPEC_SOURCE; case TYPEC_PORT_SNK: return TYPEC_SINK; case TYPEC_PORT_DRP: default: if (wusb3801->cap.prefer_role == TYPEC_SOURCE) return TYPEC_SOURCE; return TYPEC_SINK; } } static int wusb3801_map_port_type(enum typec_port_type type) { switch (type) { case TYPEC_PORT_SRC: return WUSB3801_CTRL0_ROLE_SRC; case TYPEC_PORT_SNK: return WUSB3801_CTRL0_ROLE_SNK; case TYPEC_PORT_DRP: default: return WUSB3801_CTRL0_ROLE_DRP; } } static int wusb3801_map_pwr_opmode(enum typec_pwr_opmode mode) { switch (mode) { case TYPEC_PWR_MODE_USB: default: return WUSB3801_CTRL0_CURRENT_DEFAULT; case TYPEC_PWR_MODE_1_5A: return WUSB3801_CTRL0_CURRENT_1_5A; case TYPEC_PWR_MODE_3_0A: return WUSB3801_CTRL0_CURRENT_3_0A; } } static unsigned int wusb3801_map_try_role(int role) { switch (role) { case TYPEC_NO_PREFERRED_ROLE: default: return WUSB3801_CTRL0_TRY_NONE; case TYPEC_SINK: return WUSB3801_CTRL0_TRY_SNK; case TYPEC_SOURCE: return WUSB3801_CTRL0_TRY_SRC; } } static enum typec_orientation wusb3801_unmap_orientation(unsigned int status) { switch (status & WUSB3801_STAT_ORIENTATION) { case WUSB3801_STAT_ORIENTATION_NONE: case WUSB3801_STAT_ORIENTATION_BOTH: default: return TYPEC_ORIENTATION_NONE; case WUSB3801_STAT_ORIENTATION_CC1: return TYPEC_ORIENTATION_NORMAL; case WUSB3801_STAT_ORIENTATION_CC2: return TYPEC_ORIENTATION_REVERSE; } } static enum typec_pwr_opmode wusb3801_unmap_pwr_opmode(unsigned int status) { switch (status & WUSB3801_STAT_CURRENT) { case WUSB3801_STAT_CURRENT_STANDBY: case WUSB3801_STAT_CURRENT_DEFAULT: default: return TYPEC_PWR_MODE_USB; case WUSB3801_STAT_CURRENT_1_5A: return TYPEC_PWR_MODE_1_5A; case WUSB3801_STAT_CURRENT_3_0A: return TYPEC_PWR_MODE_3_0A; } } static int wusb3801_try_role(struct typec_port *port, int role) { struct wusb3801 *wusb3801 = typec_get_drvdata(port); return regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0, WUSB3801_CTRL0_TRY, wusb3801_map_try_role(role)); } static int wusb3801_port_type_set(struct typec_port *port, enum typec_port_type type) { struct wusb3801 *wusb3801 = typec_get_drvdata(port); int ret; ret = regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0, WUSB3801_CTRL0_ROLE, wusb3801_map_port_type(type)); if (ret) return ret; wusb3801->port_type = type; return 0; } static const struct typec_operations wusb3801_typec_ops = { .try_role = wusb3801_try_role, .port_type_set = wusb3801_port_type_set, }; static int wusb3801_hw_init(struct wusb3801 *wusb3801) { return regmap_write(wusb3801->regmap, WUSB3801_REG_CTRL0, wusb3801_map_try_role(wusb3801->cap.prefer_role) | wusb3801_map_pwr_opmode(wusb3801->pwr_opmode) | wusb3801_map_port_type(wusb3801->port_type)); } static void wusb3801_hw_update(struct wusb3801 *wusb3801) { struct typec_port *port = wusb3801->port; struct device *dev = wusb3801->dev; unsigned int partner_type, status; int ret; ret = regmap_read(wusb3801->regmap, WUSB3801_REG_STAT, &status); if (ret) { dev_warn(dev, "Failed to read port status: %d\n", ret); status = 0; } dev_dbg(dev, "status = 0x%02x\n", status); partner_type = status & WUSB3801_STAT_PARTNER; if (partner_type == WUSB3801_STAT_PARTNER_SNK) { if (!wusb3801->vbus_on) { ret = regulator_enable(wusb3801->vbus_supply); if (ret) dev_warn(dev, "Failed to enable VBUS: %d\n", ret); wusb3801->vbus_on = true; } } else { if (wusb3801->vbus_on) { regulator_disable(wusb3801->vbus_supply); wusb3801->vbus_on = false; } } if (partner_type != wusb3801->partner_type) { struct typec_partner_desc desc = {}; enum typec_data_role data_role; enum typec_role pwr_role = wusb3801_get_default_role(wusb3801); switch (partner_type) { case WUSB3801_STAT_PARTNER_STANDBY: break; case WUSB3801_STAT_PARTNER_SNK: pwr_role = TYPEC_SOURCE; break; case WUSB3801_STAT_PARTNER_SRC: pwr_role = TYPEC_SINK; break; case WUSB3801_STAT_PARTNER_AUDIO: desc.accessory = TYPEC_ACCESSORY_AUDIO; break; case WUSB3801_STAT_PARTNER_DEBUG: desc.accessory = TYPEC_ACCESSORY_DEBUG; break; } if (wusb3801->partner) { typec_unregister_partner(wusb3801->partner); wusb3801->partner = NULL; } if (partner_type != WUSB3801_STAT_PARTNER_STANDBY) { wusb3801->partner = typec_register_partner(port, &desc); if (IS_ERR(wusb3801->partner)) dev_err(dev, "Failed to register partner: %ld\n", PTR_ERR(wusb3801->partner)); } data_role = pwr_role == TYPEC_SOURCE ? TYPEC_HOST : TYPEC_DEVICE; typec_set_data_role(port, data_role); typec_set_pwr_role(port, pwr_role); typec_set_vconn_role(port, pwr_role); } typec_set_pwr_opmode(wusb3801->port, partner_type == WUSB3801_STAT_PARTNER_SRC ? wusb3801_unmap_pwr_opmode(status) : wusb3801->pwr_opmode); typec_set_orientation(wusb3801->port, wusb3801_unmap_orientation(status)); wusb3801->partner_type = partner_type; } static irqreturn_t wusb3801_irq(int irq, void *data) { struct wusb3801 *wusb3801 = data; unsigned int dummy; /* * The interrupt register must be read in order to clear the IRQ, * but all of the useful information is in the status register. */ regmap_read(wusb3801->regmap, WUSB3801_REG_INT, &dummy); wusb3801_hw_update(wusb3801); return IRQ_HANDLED; } static const struct regmap_config config = { .reg_bits = 8, .val_bits = 8, .max_register = WUSB3801_REG_TEST12, }; static int wusb3801_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct fwnode_handle *connector; struct wusb3801 *wusb3801; const char *cap_str; int ret; wusb3801 = devm_kzalloc(dev, sizeof(*wusb3801), GFP_KERNEL); if (!wusb3801) return -ENOMEM; i2c_set_clientdata(client, wusb3801); wusb3801->dev = dev; wusb3801->regmap = devm_regmap_init_i2c(client, &config); if (IS_ERR(wusb3801->regmap)) return PTR_ERR(wusb3801->regmap); wusb3801->vbus_supply = devm_regulator_get(dev, "vbus"); if (IS_ERR(wusb3801->vbus_supply)) return PTR_ERR(wusb3801->vbus_supply); connector = device_get_named_child_node(dev, "connector"); if (!connector) return -ENODEV; ret = typec_get_fw_cap(&wusb3801->cap, connector); if (ret) goto err_put_connector; wusb3801->port_type = wusb3801->cap.type; ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str); if (ret) goto err_put_connector; ret = typec_find_pwr_opmode(cap_str); if (ret < 0 || ret == TYPEC_PWR_MODE_PD) goto err_put_connector; wusb3801->pwr_opmode = ret; /* Initialize the hardware with the devicetree settings. */ ret = wusb3801_hw_init(wusb3801); if (ret) goto err_put_connector; wusb3801->cap.revision = USB_TYPEC_REV_1_2; wusb3801->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO; wusb3801->cap.accessory[1] = TYPEC_ACCESSORY_DEBUG; wusb3801->cap.orientation_aware = true; wusb3801->cap.driver_data = wusb3801; wusb3801->cap.ops = &wusb3801_typec_ops; wusb3801->port = typec_register_port(dev, &wusb3801->cap); if (IS_ERR(wusb3801->port)) { ret = PTR_ERR(wusb3801->port); goto err_put_connector; } /* Initialize the port attributes from the hardware state. */ wusb3801_hw_update(wusb3801); ret = request_threaded_irq(client->irq, NULL, wusb3801_irq, IRQF_ONESHOT, dev_name(dev), wusb3801); if (ret) goto err_unregister_port; fwnode_handle_put(connector); return 0; err_unregister_port: typec_unregister_port(wusb3801->port); err_put_connector: fwnode_handle_put(connector); return ret; } static void wusb3801_remove(struct i2c_client *client) { struct wusb3801 *wusb3801 = i2c_get_clientdata(client); free_irq(client->irq, wusb3801); if (wusb3801->partner) typec_unregister_partner(wusb3801->partner); typec_unregister_port(wusb3801->port); if (wusb3801->vbus_on) regulator_disable(wusb3801->vbus_supply); } static const struct of_device_id wusb3801_of_match[] = { { .compatible = "willsemi,wusb3801" }, {} }; MODULE_DEVICE_TABLE(of, wusb3801_of_match); static struct i2c_driver wusb3801_driver = { .probe = wusb3801_probe, .remove = wusb3801_remove, .driver = { .name = "wusb3801", .of_match_table = wusb3801_of_match, }, }; module_i2c_driver(wusb3801_driver); MODULE_AUTHOR("Samuel Holland "); MODULE_DESCRIPTION("Willsemi WUSB3801 Type-C port controller driver"); MODULE_LICENSE("GPL");