// SPDX-License-Identifier: GPL-2.0 /* * * extcon-rtk-type-c.c - Realtek Extcon Type C driver * * Copyright (C) 2023 Realtek Semiconductor Corporation * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct cc_param { u32 rp_4p7k_code; u32 rp_36k_code; u32 rp_12k_code; u32 rd_code; u32 ra_code; u32 vref_2p6v; u32 vref_1p23v; u32 vref_0p8v; u32 vref_0p66v; u32 vref_0p4v; u32 vref_0p2v; u32 vref_1_1p6v; u32 vref_0_1p6v; }; struct type_c_cfg { int parameter_ver; /* Parameter version */ int cc_dfp_mode; struct cc_param cc1_param; struct cc_param cc2_param; u32 debounce_val; bool use_defalut_parameter; }; struct type_c_data { void __iomem *reg_base; struct device *dev; struct extcon_dev *edev; u32 irq; /* rd control GPIO only for rtd1295 */ struct gpio_desc *rd_ctrl_gpio_desc; /* Parameters */ struct type_c_cfg *type_c_cfg; u32 dfp_mode_rp_en; u32 ufp_mode_rd_en; u32 cc1_code; u32 cc2_code; u32 cc1_vref; u32 cc2_vref; u32 debounce; /* 1b,1us 7f,4.7us */ /* type_c state */ int connect_change; #define CONNECT_CHANGE 1 #define CONNECT_NO_CHANGE 0 int cc_mode; /* cc is host or device */ #define IN_HOST_MODE 0x10 #define IN_DEVICE_MODE 0x20 int is_attach; #define IN_ATTACH 1 #define TO_ATTACH 1 #define IN_DETACH 0 #define TO_DETACH 0 int at_cc1; #define AT_CC1 1 #define AT_CC2 0 u32 int_status; u32 cc_status; /* protect the data member */ spinlock_t lock; struct delayed_work delayed_work; bool rd_en_at_first; struct dentry *debug_dir; struct typec_port *port; }; /* Type C register offset */ #define USB_TYPEC_CTRL_CC1_0 0x0 #define USB_TYPEC_CTRL_CC1_1 0x4 #define USB_TYPEC_CTRL_CC2_0 0x8 #define USB_TYPEC_CTRL_CC2_1 0xC #define USB_TYPEC_STS 0x10 #define USB_TYPEC_CTRL 0x14 #define USB_DBUS_PWR_CTRL 0x18 #define ENABLE_CC1 0x1 #define ENABLE_CC2 0x2 #define DISABLE_CC 0x0 /* Bit mapping USB_TYPEC_CTRL_CC1_0 and USB_TYPEC_CTRL_CC2_0 */ #define PLR_EN BIT(29) #define CC_SWITCH_MASK (BIT(29) | BIT(28) | BIT(27)) #define CC_CODE_MASK (0xfffff << 7) #define rp4pk_code(val) ((0x1f & (val)) << 22) #define code_rp4pk(val) (((val) >> 22) & 0x1f) #define rp36k_code(val) ((0x1f & (val)) << 17) #define code_rp36k(val) (((val) >> 17) & 0x1f) #define rp12k_code(val) ((0x1f & (val)) << 12) #define code_rp12k(val) (((val) >> 12) & 0x1f) #define rd_code(val) ((0x1f & (val)) << 7) #define code_rd(val) (((val) >> 7) & 0x1f) #define dfp_mode(val) ((0x3 & (val)) << 5) #define EN_RP4P7K BIT(4) #define EN_RP36K BIT(3) #define EN_RP12K BIT(2) #define EN_RD BIT(1) #define EN_CC_DET BIT(0) #define CC_MODE_UFP 0x0 #define CC_MODE_DFP_USB 0x1 #define CC_MODE_DFP_1_5 0x2 #define CC_MODE_DFP_3_0 0x3 /* * PARAMETER_V0: * Realtek Kylin rtd1295 * Realtek Hercules rtd1395 * Realtek Thor rtd1619 * Realtek Hank rtd1319 * Realtek Groot rtd1312c * PARAMETER_V1: * Realtek Stark rtd1619b * Realtek Parker rtd1319d * Realtek Danvers rtd1315e */ enum parameter_version { PARAMETER_V0 = 0, PARAMETER_V1 = 1, }; /* Bit mapping USB_TYPEC_CTRL_CC1_1 and USB_TYPEC_CTRL_CC2_1 */ #define V0_vref_2p6v(val) ((0xf & (val)) << 26) /* Bit 29 for groot */ #define V0_vref_1p23v(val) ((0xf & (val)) << 22) #define V0_vref_0p8v(val) ((0xf & (val)) << 18) #define V0_vref_0p66v(val) ((0xf & (val)) << 14) #define V0_vref_0p4v(val) ((0x7 & (val)) << 11) #define V0_vref_0p2v(val) ((0x7 & (val)) << 8) #define V0_vref_1_1p6v(val) ((0xf & (val)) << 4) #define V0_vref_0_1p6v(val) ((0xf & (val)) << 0) #define V0_decode_2p6v(val) (((val) >> 26) & 0xf) /* Bit 29 for groot */ #define V0_decode_1p23v(val) (((val) >> 22) & 0xf) #define V0_decode_0p8v(val) (((val) >> 18) & 0xf) #define V0_decode_0p66v(val) (((val) >> 14) & 0xf) #define V0_decode_0p4v(val) (((val) >> 11) & 0x7) #define V0_decode_0p2v(val) (((val) >> 8) & 0x7) #define V0_decode_1_1p6v(val) (((val) >> 4) & 0xf) #define V0_decode_0_1p6v(val) (((val) >> 0) & 0xf) /* new Bit mapping USB_TYPEC_CTRL_CC1_1 and USB_TYPEC_CTRL_CC2_1 */ #define V1_vref_2p6v(val) ((0xf & (val)) << 28) #define V1_vref_1p23v(val) ((0xf & (val)) << 24) #define V1_vref_0p8v(val) ((0xf & (val)) << 20) #define V1_vref_0p66v(val) ((0xf & (val)) << 16) #define V1_vref_0p4v(val) ((0xf & (val)) << 12) #define V1_vref_0p2v(val) ((0xf & (val)) << 8) #define V1_vref_1_1p6v(val) ((0xf & (val)) << 4) #define V1_vref_0_1p6v(val) ((0xf & (val)) << 0) #define V1_decode_2p6v(val) (((val) >> 28) & 0xf) #define V1_decode_1p23v(val) (((val) >> 24) & 0xf) #define V1_decode_0p8v(val) (((val) >> 20) & 0xf) #define V1_decode_0p66v(val) (((val) >> 16) & 0xf) #define V1_decode_0p4v(val) (((val) >> 12) & 0xf) #define V1_decode_0p2v(val) (((val) >> 8) & 0xf) #define V1_decode_1_1p6v(val) (((val) >> 4) & 0xf) #define V1_decode_0_1p6v(val) (((val) >> 0) & 0xf) /* Bit mapping USB_TYPEC_STS */ #define DET_STS 0x7 #define CC1_DET_STS (DET_STS) #define CC2_DET_STS (DET_STS << 3) #define DET_STS_RA 0x1 #define DET_STS_RD 0x3 #define DET_STS_RP 0x1 #define CC1_DET_STS_RA (DET_STS_RA) #define CC1_DET_STS_RD (DET_STS_RD) #define CC1_DET_STS_RP (DET_STS_RP) #define CC2_DET_STS_RA (DET_STS_RA << 3) #define CC2_DET_STS_RD (DET_STS_RD << 3) #define CC2_DET_STS_RP (DET_STS_RP << 3) /* Bit mapping USB_TYPEC_CTRL */ #define CC2_INT_EN BIT(11) #define CC1_INT_EN BIT(10) #define CC2_INT_STS BIT(9) #define CC1_INT_STS BIT(8) #define DEBOUNCE_TIME_MASK 0xff #define DEBOUNCE_EN BIT(0) #define ENABLE_TYPE_C_DETECT (CC1_INT_EN | CC2_INT_EN) #define ALL_CC_INT_STS (CC1_INT_STS | CC2_INT_STS) /* Parameter */ #define DETECT_TIME 50 /* ms */ static const unsigned int usb_type_c_cable[] = { EXTCON_USB, EXTCON_USB_HOST, EXTCON_NONE, }; enum usb_data_roles { DR_NONE, DR_HOST, DR_DEVICE, }; static const struct soc_device_attribute rtk_soc_kylin[] = { { .family = "Realtek Kylin", }, { /* empty */ } }; static int rtd129x_switch_type_c_plug_config(struct type_c_data *type_c, int dr_mode, int cc) { void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_0; int val_cc; #define TYPE_C_EN_SWITCH BIT(29) #define TYPE_C_TXRX_SEL (BIT(28) | BIT(27)) #define TYPE_C_SWITCH_MASK (TYPE_C_EN_SWITCH | TYPE_C_TXRX_SEL) #define TYPE_C_ENABLE_CC1 TYPE_C_EN_SWITCH #define TYPE_C_ENABLE_CC2 (TYPE_C_EN_SWITCH | TYPE_C_TXRX_SEL) #define TYPE_C_DISABLE_CC ~TYPE_C_SWITCH_MASK val_cc = readl(reg); val_cc &= ~TYPE_C_SWITCH_MASK; if (cc == DISABLE_CC) { val_cc &= TYPE_C_DISABLE_CC; } else if (cc == ENABLE_CC1) { val_cc |= TYPE_C_ENABLE_CC1; } else if (cc == ENABLE_CC2) { val_cc |= TYPE_C_ENABLE_CC2; } else { dev_err(type_c->dev, "%s: Error cc setting cc=0x%x\n", __func__, cc); return -EINVAL; } writel(val_cc, reg); /* waiting cc stable for enable/disable */ mdelay(1); dev_dbg(type_c->dev, "%s: cc=0x%x val_cc=0x%x usb_typec_ctrl_cc1_0=0x%x\n", __func__, cc, val_cc, readl(reg)); return 0; } static inline void switch_type_c_plug_config(struct type_c_data *type_c, int dr_mode, int cc) { int ret = 0; if (soc_device_match(rtk_soc_kylin)) ret = rtd129x_switch_type_c_plug_config(type_c, dr_mode, cc); if (ret < 0) dev_err(type_c->dev, "%s: Error set type c plug config\n", __func__); } static void switch_type_c_dr_mode(struct type_c_data *type_c, int dr_mode, int cc) { bool is_host = false; bool is_device = false; bool polarity = false; bool vbus = false; bool ss = true; switch_type_c_plug_config(type_c, dr_mode, cc); if (cc == ENABLE_CC2) polarity = true; switch (dr_mode) { case USB_DR_MODE_HOST: is_host = true; break; case USB_DR_MODE_PERIPHERAL: is_device = true; vbus = true; break; default: dev_dbg(type_c->dev, "%s dr_mode=%d ==> no host or device\n", __func__, dr_mode); break; } dev_dbg(type_c->dev, "%s is_host=%d is_device=%d vbus=%d polarity=%d\n", __func__, is_host, is_device, vbus, polarity); /* for EXTCON_USB device mode */ extcon_set_state(type_c->edev, EXTCON_USB, is_device); extcon_set_property(type_c->edev, EXTCON_USB, EXTCON_PROP_USB_VBUS, (union extcon_property_value)(int)vbus); extcon_set_property(type_c->edev, EXTCON_USB, EXTCON_PROP_USB_TYPEC_POLARITY, (union extcon_property_value)(int)polarity); extcon_set_property(type_c->edev, EXTCON_USB, EXTCON_PROP_USB_SS, (union extcon_property_value)(int)ss); /* for EXTCON_USB_HOST host mode */ extcon_set_state(type_c->edev, EXTCON_USB_HOST, is_host); extcon_set_property(type_c->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_VBUS, (union extcon_property_value)(int)vbus); extcon_set_property(type_c->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_TYPEC_POLARITY, (union extcon_property_value)(int)polarity); extcon_set_property(type_c->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_SS, (union extcon_property_value)(int)ss); /* sync EXTCON_USB and EXTCON_USB_HOST */ extcon_sync(type_c->edev, EXTCON_USB); extcon_sync(type_c->edev, EXTCON_USB_HOST); if (type_c->port) { switch (dr_mode) { case USB_DR_MODE_HOST: typec_set_data_role(type_c->port, TYPEC_HOST); typec_set_pwr_role(type_c->port, TYPEC_SOURCE); break; case USB_DR_MODE_PERIPHERAL: typec_set_data_role(type_c->port, TYPEC_DEVICE); typec_set_pwr_role(type_c->port, TYPEC_SINK); break; default: dev_dbg(type_c->dev, "%s unknown dr_mode=%d\n", __func__, dr_mode); break; } } } /* connector attached/detached */ static int connector_attached(struct type_c_data *type_c, u32 cc, int dr_mode) { void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL; cancel_delayed_work(&type_c->delayed_work); switch_type_c_dr_mode(type_c, dr_mode, cc); writel(ENABLE_TYPE_C_DETECT | readl(reg), reg); return 0; } static int connector_detached(struct type_c_data *type_c, u32 cc, int dr_mode) { void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL; writel(~ENABLE_TYPE_C_DETECT & readl(reg), reg); switch_type_c_dr_mode(type_c, 0, cc); schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(DETECT_TIME)); return 0; } /* detect host device switch */ static int __detect_host_device(struct type_c_data *type_c, u32 rp_or_rd_en) { struct device *dev = type_c->dev; void __iomem *reg_base = type_c->reg_base; u32 cc1_config, cc2_config, default_ctrl; u32 cc1_switch = 0; default_ctrl = readl(reg_base + USB_TYPEC_CTRL) & DEBOUNCE_TIME_MASK; writel(default_ctrl, reg_base + USB_TYPEC_CTRL); cc1_config = readl(reg_base + USB_TYPEC_CTRL_CC1_0); cc2_config = readl(reg_base + USB_TYPEC_CTRL_CC2_0); cc1_config &= ~EN_CC_DET; cc2_config &= ~EN_CC_DET; writel(cc1_config, reg_base + USB_TYPEC_CTRL_CC1_0); writel(cc2_config, reg_base + USB_TYPEC_CTRL_CC2_0); if (soc_device_match(rtk_soc_kylin)) cc1_switch = cc1_config & CC_SWITCH_MASK; cc1_config &= CC_CODE_MASK; cc1_config |= rp_or_rd_en | cc1_switch; cc2_config &= CC_CODE_MASK; cc2_config |= rp_or_rd_en; writel(cc2_config, reg_base + USB_TYPEC_CTRL_CC2_0); writel(cc1_config, reg_base + USB_TYPEC_CTRL_CC1_0); /* For kylin to disable external rd control gpio */ if (soc_device_match(rtk_soc_kylin)) { struct gpio_desc *gpio = type_c->rd_ctrl_gpio_desc; if (gpio && gpiod_direction_output(gpio, 1)) dev_err(dev, "%s ERROR set rd_ctrl_gpio_desc fail\n", __func__); } cc1_config |= EN_CC_DET; cc2_config |= EN_CC_DET; writel(cc1_config, reg_base + USB_TYPEC_CTRL_CC1_0); writel(cc2_config, reg_base + USB_TYPEC_CTRL_CC2_0); return 0; } static int detect_device(struct type_c_data *type_c) { return __detect_host_device(type_c, type_c->dfp_mode_rp_en); } static int detect_host(struct type_c_data *type_c) { return __detect_host_device(type_c, type_c->ufp_mode_rd_en); } static int host_device_switch_detection(struct type_c_data *type_c) { if (type_c->cc_mode == IN_HOST_MODE) { type_c->cc_mode = IN_DEVICE_MODE; detect_host(type_c); } else { type_c->cc_mode = IN_HOST_MODE; detect_device(type_c); } return 0; } static int detect_type_c_state(struct type_c_data *type_c) { struct device *dev = type_c->dev; void __iomem *reg_base = type_c->reg_base; u32 int_status, cc_status, cc_status_check; unsigned long flags; spin_lock_irqsave(&type_c->lock, flags); int_status = readl(reg_base + USB_TYPEC_CTRL); cc_status = readl(reg_base + USB_TYPEC_STS); type_c->connect_change = CONNECT_NO_CHANGE; switch (type_c->cc_mode | type_c->is_attach) { case IN_HOST_MODE | IN_ATTACH: if (((cc_status & CC1_DET_STS) == CC1_DET_STS) && type_c->at_cc1 == AT_CC1) { dev_dbg(dev, "IN host mode and cc1 device detach (cc_status=0x%x)", cc_status); type_c->is_attach = TO_DETACH; type_c->connect_change = CONNECT_CHANGE; } else if (((cc_status & CC2_DET_STS) == CC2_DET_STS) && type_c->at_cc1 == AT_CC2) { dev_dbg(dev, "IN host mode and cc2 device detach (cc_status=0x%x)", cc_status); type_c->is_attach = TO_DETACH; type_c->connect_change = CONNECT_CHANGE; } break; case IN_HOST_MODE | IN_DETACH: cc_status_check = readl(reg_base + USB_TYPEC_STS); if (cc_status_check != (CC1_DET_STS | CC2_DET_STS)) { if (in_interrupt()) { /* Add delay time to avoid capacitive effect of cable. */ mdelay(300); } else { spin_unlock_irqrestore(&type_c->lock, flags); /* Add delay time to avoid capacitive effect of cable. */ msleep(300); spin_lock_irqsave(&type_c->lock, flags); } cc_status_check = readl(reg_base + USB_TYPEC_STS); } if (cc_status != cc_status_check) { dev_warn(dev, "IN_HOST_MODE: cc_status (0x%x) != cc_status_check (0x%x)\n", cc_status, cc_status_check); cc_status = readl(reg_base + USB_TYPEC_STS); } if ((cc_status & CC1_DET_STS) == CC1_DET_STS_RD) { dev_dbg(dev, "IN host mode and cc1 device attach (cc_status=0x%x)", cc_status); type_c->is_attach = TO_ATTACH; type_c->at_cc1 = AT_CC1; type_c->connect_change = CONNECT_CHANGE; } else if ((cc_status & CC2_DET_STS) == CC2_DET_STS_RD) { dev_dbg(dev, "In host mode and cc2 device attach (cc_status=0x%x)", cc_status); type_c->is_attach = TO_ATTACH; type_c->at_cc1 = AT_CC2; type_c->connect_change = CONNECT_CHANGE; } break; case IN_DEVICE_MODE | IN_ATTACH: if ((cc_status & CC1_DET_STS) < CC1_DET_STS_RP || (cc_status & CC2_DET_STS) < CC2_DET_STS_RP) { /* Add a sw debounce to filter cc signal sent from apple pd adapter */ mdelay(5); cc_status_check = readl(reg_base + USB_TYPEC_STS); if (cc_status != cc_status_check) { dev_dbg(dev, "IN_DEVICE_MODE: cc_status (0x%x) != cc_status_check (0x%x) maybe use a pd adapter\n", cc_status, cc_status_check); cc_status = cc_status_check; } } if ((cc_status & CC1_DET_STS) < CC1_DET_STS_RP && type_c->at_cc1 == AT_CC1) { dev_dbg(dev, "IN device mode and cc1 host disconnect (cc_status=0x%x)", cc_status); type_c->is_attach = TO_DETACH; type_c->connect_change = CONNECT_CHANGE; } else if ((cc_status & CC2_DET_STS) < CC2_DET_STS_RP && type_c->at_cc1 == AT_CC2) { dev_dbg(dev, "IN device mode and cc2 host disconnect (cc_status=0x%x)", cc_status); type_c->is_attach = TO_DETACH; type_c->connect_change = CONNECT_CHANGE; } break; case IN_DEVICE_MODE | IN_DETACH: cc_status_check = readl(reg_base + USB_TYPEC_STS); if (cc_status_check != 0x0) { if (in_interrupt()) { /* Add delay time to avoid capacitive effect of cable. */ mdelay(300); } else { spin_unlock_irqrestore(&type_c->lock, flags); /* Add delay time to avoid capacitive effect of cable. */ msleep(300); spin_lock_irqsave(&type_c->lock, flags); } cc_status_check = readl(reg_base + USB_TYPEC_STS); } if (cc_status != cc_status_check) { dev_warn(dev, "IN_DEVICE_MODE: cc_status (0x%x) != cc_status_check (0x%x)\n", cc_status, cc_status_check); cc_status = readl(reg_base + USB_TYPEC_STS); } if ((cc_status & CC1_DET_STS) >= CC1_DET_STS_RP) { dev_dbg(dev, "IN device mode and cc1 host connect (cc_status=0x%x)", cc_status); type_c->at_cc1 = AT_CC1; type_c->is_attach = TO_ATTACH; type_c->connect_change = CONNECT_CHANGE; } else if ((cc_status & CC2_DET_STS) >= CC2_DET_STS_RP) { dev_dbg(dev, "IN device mode and cc2 host connect (cc_status=0x%x)", cc_status); type_c->at_cc1 = AT_CC2; type_c->is_attach = TO_ATTACH; type_c->connect_change = CONNECT_CHANGE; } break; default: dev_err(dev, "error host or device mode (cc_mode=%d, is_attach=%d) ", type_c->cc_mode, type_c->is_attach); } type_c->int_status = int_status; type_c->cc_status = cc_status; spin_unlock_irqrestore(&type_c->lock, flags); return 0; } static void host_device_switch(struct work_struct *work) { struct type_c_data *type_c = container_of(work, struct type_c_data, delayed_work.work); struct device *dev = type_c->dev; unsigned long flags; int connect_change = 0; int cc_mode = 0; int is_attach = 0; int at_cc1 = 0; spin_lock_irqsave(&type_c->lock, flags); if (type_c->connect_change) connect_change = type_c->connect_change; spin_unlock_irqrestore(&type_c->lock, flags); if (!connect_change) detect_type_c_state(type_c); spin_lock_irqsave(&type_c->lock, flags); if (type_c->connect_change) { connect_change = type_c->connect_change; cc_mode = type_c->cc_mode; is_attach = type_c->is_attach; at_cc1 = type_c->at_cc1; type_c->connect_change = CONNECT_NO_CHANGE; } else { host_device_switch_detection(type_c); schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(DETECT_TIME)); } spin_unlock_irqrestore(&type_c->lock, flags); if (!connect_change) return; dev_dbg(dev, "%s: usb cable connection change\n", __func__); if (cc_mode == IN_HOST_MODE) { if (is_attach && at_cc1) connector_attached(type_c, ENABLE_CC1, USB_DR_MODE_HOST); else if (is_attach && !at_cc1) connector_attached(type_c, ENABLE_CC2, USB_DR_MODE_HOST); else connector_detached(type_c, DISABLE_CC, USB_DR_MODE_HOST); } else if (cc_mode == IN_DEVICE_MODE) { if (is_attach && at_cc1) connector_attached(type_c, ENABLE_CC1, USB_DR_MODE_PERIPHERAL); else if (is_attach && !at_cc1) connector_attached(type_c, ENABLE_CC2, USB_DR_MODE_PERIPHERAL); else connector_detached(type_c, DISABLE_CC, USB_DR_MODE_PERIPHERAL); } else { dev_err(dev, "Error: IN unknown mode %d to %s at %s (cc_status=0x%x)\n", cc_mode, is_attach ? "attach" : "detach", at_cc1 ? "cc1" : "cc2", type_c->cc_status); } dev_info(dev, "Connection change OK: IN %s mode to %s at %s (cc_status=0x%x)\n", cc_mode == IN_HOST_MODE ? "host" : "device", is_attach ? "attach" : "detach", at_cc1 ? "cc1" : "cc2", type_c->cc_status); } static irqreturn_t type_c_detect_irq(int irq, void *__data) { struct type_c_data *type_c = (struct type_c_data *)__data; struct device *dev = type_c->dev; void __iomem *reg = type_c->reg_base + USB_TYPEC_CTRL; unsigned long flags; detect_type_c_state(type_c); spin_lock_irqsave(&type_c->lock, flags); if (type_c->connect_change) { dev_dbg(dev, "%s: IN %s mode to %s (at %s interrupt) int_status=0x%x, cc_status=0x%x", __func__, type_c->cc_mode == IN_HOST_MODE ? "host" : "device", type_c->is_attach ? "attach" : "detach", type_c->at_cc1 ? "cc1" : "cc2", type_c->int_status, type_c->cc_status); /* clear interrupt status */ writel(~ALL_CC_INT_STS & readl(reg), reg); cancel_delayed_work(&type_c->delayed_work); schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(0)); } else { static int local_count; /* if no connect_change, we keep the status to avoid status lose */ if (local_count++ > 10) { /* clear interrupt status */ writel(~ALL_CC_INT_STS & readl(reg), reg); local_count = 0; } } spin_unlock_irqrestore(&type_c->lock, flags); return IRQ_HANDLED; } static int type_c_port_dr_set(struct typec_port *port, enum typec_data_role role) { struct type_c_data *type_c = typec_get_drvdata(port); u32 enable_cc; unsigned long flags; spin_lock_irqsave(&type_c->lock, flags); enable_cc = type_c->at_cc1 ? ENABLE_CC1 : ENABLE_CC2; spin_unlock_irqrestore(&type_c->lock, flags); if (role == TYPEC_HOST) switch_type_c_dr_mode(type_c, USB_DR_MODE_HOST, enable_cc); else if (role == TYPEC_DEVICE) switch_type_c_dr_mode(type_c, USB_DR_MODE_PERIPHERAL, enable_cc); else switch_type_c_dr_mode(type_c, 0, DISABLE_CC); return 0; } static const struct typec_operations type_c_port_ops = { .dr_set = type_c_port_dr_set, }; #ifdef CONFIG_DEBUG_FS static int type_c_parameter_show(struct seq_file *s, void *unused) { struct type_c_data *type_c = s->private; struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; struct cc_param *cc_param; unsigned long flags; spin_lock_irqsave(&type_c->lock, flags); seq_printf(s, "cc_dfp_mode %s\n", ({ char *tmp; switch (type_c_cfg->cc_dfp_mode) { case CC_MODE_DFP_USB: tmp = "CC_MODE_DFP_USB"; break; case CC_MODE_DFP_1_5: tmp = "CC_MODE_DFP_1_5"; break; case CC_MODE_DFP_3_0: tmp = "CC_MODE_DFP_3_0"; break; default: tmp = "?"; break; } tmp; })); seq_printf(s, "dfp_mode_rp_en 0x%x\n", type_c->dfp_mode_rp_en); seq_printf(s, "ufp_mode_rd_en 0x%x\n", type_c->ufp_mode_rd_en); seq_printf(s, "cc1_code 0x%x\n", type_c->cc1_code); seq_printf(s, "cc2_code 0x%x\n", type_c->cc2_code); seq_printf(s, "cc1_vref 0x%x\n", type_c->cc1_vref); seq_printf(s, "cc2_vref 0x%x\n", type_c->cc2_vref); seq_printf(s, "debounce 0x%x\n", type_c->debounce); seq_puts(s, "\n"); cc_param = &type_c_cfg->cc1_param; seq_puts(s, "cc1_param:\n"); seq_printf(s, " rp_4p7k_code 0x%x\n", cc_param->rp_4p7k_code); seq_printf(s, " rp_36k_code 0x%x\n", cc_param->rp_36k_code); seq_printf(s, " rp_12k_code 0x%x\n", cc_param->rp_12k_code); seq_printf(s, " rd_code 0x%x\n", cc_param->rd_code); seq_printf(s, " vref_2p6v 0x%x\n", cc_param->vref_2p6v); seq_printf(s, " vref_1p23v 0x%x\n", cc_param->vref_1p23v); seq_printf(s, " vref_0p8v 0x%x\n", cc_param->vref_0p8v); seq_printf(s, " vref_0p66v 0x%x\n", cc_param->vref_0p66v); seq_printf(s, " vref_0p4v 0x%x\n", cc_param->vref_0p4v); seq_printf(s, " vref_0p2v 0x%x\n", cc_param->vref_0p2v); seq_printf(s, " vref_1_1p6v 0x%x\n", cc_param->vref_1_1p6v); seq_printf(s, " vref_0_1p6v 0x%x\n", cc_param->vref_0_1p6v); cc_param = &type_c_cfg->cc2_param; seq_puts(s, "cc2_param:\n"); seq_printf(s, " rp_4p7k_code 0x%x\n", cc_param->rp_4p7k_code); seq_printf(s, " rp_36k_code 0x%x\n", cc_param->rp_36k_code); seq_printf(s, " rp_12k_code 0x%x\n", cc_param->rp_12k_code); seq_printf(s, " rd_code 0x%x\n", cc_param->rd_code); seq_printf(s, " vref_2p6v 0x%x\n", cc_param->vref_2p6v); seq_printf(s, " vref_1p23v 0x%x\n", cc_param->vref_1p23v); seq_printf(s, " vref_0p8v 0x%x\n", cc_param->vref_0p8v); seq_printf(s, " vref_0p66v 0x%x\n", cc_param->vref_0p66v); seq_printf(s, " vref_0p4v 0x%x\n", cc_param->vref_0p4v); seq_printf(s, " vref_0p2v 0x%x\n", cc_param->vref_0p2v); seq_printf(s, " vref_1_1p6v 0x%x\n", cc_param->vref_1_1p6v); seq_printf(s, " vref_0_1p6v 0x%x\n", cc_param->vref_0_1p6v); spin_unlock_irqrestore(&type_c->lock, flags); return 0; } static int type_c_parameter_open(struct inode *inode, struct file *file) { return single_open(file, type_c_parameter_show, inode->i_private); } static const struct file_operations type_c_parameter_fops = { .open = type_c_parameter_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int type_c_status_show(struct seq_file *s, void *unused) { struct type_c_data *type_c = s->private; unsigned long flags; spin_lock_irqsave(&type_c->lock, flags); seq_printf(s, "In %s mode %s at %s (cc_status=0x%x)\n", type_c->cc_mode == IN_HOST_MODE ? "host" : "device", type_c->is_attach ? "attach" : "detach", type_c->at_cc1 ? "cc1" : "cc2", type_c->cc_status); seq_printf(s, "Read Register (type_c_ctrl_cc1_0=0x%x)\n", readl(type_c->reg_base + 0x0)); seq_printf(s, "Read Register (type_c_ctrl_cc1_1=0x%x)\n", readl(type_c->reg_base + 0x4)); seq_printf(s, "Read Register (type_c_ctrl_cc2_0=0x%x)\n", readl(type_c->reg_base + 0x8)); seq_printf(s, "Read Register (type_c_ctrl_cc2_1=0x%x)\n", readl(type_c->reg_base + 0xc)); seq_printf(s, "Read Register (type_c_status=0x%x)\n", readl(type_c->reg_base + 0x10)); seq_printf(s, "Read Register (type_c_ctrl=0x%x)\n", readl(type_c->reg_base + 0x14)); spin_unlock_irqrestore(&type_c->lock, flags); return 0; } static int type_c_status_open(struct inode *inode, struct file *file) { return single_open(file, type_c_status_show, inode->i_private); } static const struct file_operations type_c_status_fops = { .open = type_c_status_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static inline void create_debug_files(struct type_c_data *type_c) { type_c->debug_dir = debugfs_create_dir("type_c", usb_debug_root); debugfs_create_file("parameter", 0444, type_c->debug_dir, type_c, &type_c_parameter_fops); debugfs_create_file("status", 0444, type_c->debug_dir, type_c, &type_c_status_fops); } static inline void remove_debug_files(struct type_c_data *type_c) { debugfs_remove_recursive(type_c->debug_dir); } #else static inline void create_debug_files(struct type_c_data *type_c) { } static inline void remove_debug_files(struct type_c_data *type_c) { } #endif /* CONFIG_DEBUG_FS */ /* Init and probe */ static inline s8 get_value(s8 value) { return (((s8)value & 0x8) ? (-(s8)(0x7 & value)) : ((s8)(value))); } static int __updated_type_c_parameter_by_efuse(struct type_c_data *type_c) { struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; struct cc_param *cc_param; struct nvmem_cell *cell; s8 cc1_4p7k = 0; s8 cc1_12k = 0; s8 cc1_0p2v = 0; s8 cc1_0p8v = 0; s8 cc1_2p6v = 0; s8 cc1_0p66v = 0; s8 cc1_1p23v = 0; s8 cc2_4p7k = 0; s8 cc2_12k = 0; s8 cc2_0p2v = 0; s8 cc2_0p8v = 0; s8 cc2_2p6v = 0; s8 cc2_0p66v = 0; s8 cc2_1p23v = 0; cell = nvmem_cell_get(type_c->dev, "usb-cal"); if (IS_ERR(cell)) { dev_warn(type_c->dev, "%s failed to get usb-cal: %ld\n", __func__, PTR_ERR(cell)); } else { unsigned char *buf; size_t buf_size; int value_size = 4; int value_mask = (BIT(value_size) - 1); buf = nvmem_cell_read(cell, &buf_size); if (!IS_ERR(buf)) { cc1_0p2v = get_value((buf[0] >> value_size * 0) & value_mask); cc1_0p8v = get_value((buf[0] >> value_size * 1) & value_mask); cc1_2p6v = get_value((buf[1] >> value_size * 0) & value_mask); cc1_0p66v = get_value((buf[1] >> value_size * 1) & value_mask); cc1_1p23v = get_value((buf[2] >> value_size * 0) & value_mask); cc2_0p2v = get_value((buf[3] >> value_size * 0) & value_mask); cc2_0p8v = get_value((buf[3] >> value_size * 1) & value_mask); cc2_2p6v = get_value((buf[4] >> value_size * 0) & value_mask); cc2_0p66v = get_value((buf[4] >> value_size * 1) & value_mask); cc2_1p23v = get_value((buf[5] >> value_size * 0) & value_mask); cc1_4p7k = get_value((buf[6] >> value_size * 0) & value_mask); cc1_12k = get_value((buf[6] >> value_size * 1) & value_mask); cc2_4p7k = get_value((buf[7] >> value_size * 0) & value_mask); cc2_12k = get_value((buf[7] >> value_size * 1) & value_mask); kfree(buf); } nvmem_cell_put(cell); } dev_dbg(type_c->dev, "check efuse cc1_4p7k=%d cc1_12k=%d cc2_4p7k=%d cc2_12k=%d\n", cc1_4p7k, cc1_12k, cc2_4p7k, cc2_12k); dev_dbg(type_c->dev, "check efuse cc1_0p2v=%d cc1_0p8v=%d cc1_2p6v=%d cc1_0p66v=%d cc1_1p23v=%d\n", cc1_0p2v, cc1_0p8v, cc1_2p6v, cc1_0p66v, cc1_1p23v); dev_dbg(type_c->dev, "check efuse cc2_0p2v=%d cc2_0p8v=%d cc2_2p6v=%d cc2_0p66v=%d cc2_1p23v=%d\n", cc2_0p2v, cc2_0p8v, cc2_2p6v, cc2_0p66v, cc2_1p23v); cc_param = &type_c_cfg->cc1_param; cc_param->rp_4p7k_code = cc_param->rp_4p7k_code + cc1_4p7k; cc_param->rp_12k_code = cc_param->rp_12k_code + cc1_12k; cc_param->vref_1p23v = cc_param->vref_1p23v + cc1_1p23v; cc_param->vref_0p66v = cc_param->vref_0p66v + cc1_0p66v; cc_param->vref_2p6v = cc_param->vref_2p6v + cc1_2p6v; cc_param->vref_0p8v = cc_param->vref_0p8v + cc1_0p8v; cc_param->vref_0p2v = cc_param->vref_0p2v + cc1_0p2v; cc_param = &type_c_cfg->cc2_param; cc_param->rp_4p7k_code = cc_param->rp_4p7k_code + cc2_4p7k; cc_param->rp_12k_code = cc_param->rp_12k_code + cc2_12k; cc_param->vref_1p23v = cc_param->vref_1p23v + cc2_1p23v; cc_param->vref_0p66v = cc_param->vref_0p66v + cc2_0p66v; cc_param->vref_2p6v = cc_param->vref_2p6v + cc2_2p6v; cc_param->vref_0p8v = cc_param->vref_0p8v + cc2_0p8v; cc_param->vref_0p2v = cc_param->vref_0p2v + cc2_0p2v; return 0; } static int __updated_type_c_parameter_by_efuse_v2(struct type_c_data *type_c) { struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; struct cc_param *cc_param; struct nvmem_cell *cell; s8 cc1_4p7k = 0; s8 cc1_12k = 0; s8 cc1_0p2v = 0; s8 cc1_0p8v = 0; s8 cc1_2p6v = 0; s8 cc1_0p66v = 0; s8 cc1_1p23v = 0; s8 cc2_4p7k = 0; s8 cc2_12k = 0; s8 cc2_0p2v = 0; s8 cc2_0p8v = 0; s8 cc2_2p6v = 0; s8 cc2_0p66v = 0; s8 cc2_1p23v = 0; cell = nvmem_cell_get(type_c->dev, "usb-type-c-cal"); if (IS_ERR(cell)) { dev_warn(type_c->dev, "%s failed to get usb-type-c-cal: %ld\n", __func__, PTR_ERR(cell)); } else { unsigned char *buf; size_t buf_size; int value_size = 0; int value_mask = (BIT(value_size) - 1); buf = nvmem_cell_read(cell, &buf_size); if (!IS_ERR(buf)) { value_size = 5; value_mask = (BIT(value_size) - 1); cc1_4p7k = buf[0] & value_mask; cc1_12k = buf[1] & value_mask; cc2_4p7k = buf[2] & value_mask; cc2_12k = buf[3] & value_mask; value_size = 4; value_mask = (BIT(value_size) - 1); cc1_0p2v = (buf[4] >> value_size * 0) & value_mask; cc1_0p66v = (buf[4] >> value_size * 1) & value_mask; cc1_0p8v = (buf[5] >> value_size * 0) & value_mask; cc1_1p23v = (buf[5] >> value_size * 1) & value_mask; cc1_2p6v = (buf[6] >> value_size * 0) & value_mask; cc2_0p2v = (buf[6] >> value_size * 1) & value_mask; cc2_0p66v = (buf[7] >> value_size * 0) & value_mask; cc2_0p8v = (buf[7] >> value_size * 1) & value_mask; cc2_1p23v = (buf[8] >> value_size * 0) & value_mask; cc2_2p6v = (buf[8] >> value_size * 1) & value_mask; kfree(buf); } nvmem_cell_put(cell); } dev_dbg(type_c->dev, "check efuse v2 cc1_4p7k=%d cc1_12k=%d cc2_4p7k=%d cc2_12k=%d\n", cc1_4p7k, cc1_12k, cc2_4p7k, cc2_12k); dev_dbg(type_c->dev, "check efuse v2 cc1_0p2v=%d cc1_0p8v=%d cc1_2p6v=%d cc1_0p66v=%d cc1_1p23v=%d\n", cc1_0p2v, cc1_0p8v, cc1_2p6v, cc1_0p66v, cc1_1p23v); dev_dbg(type_c->dev, "check efuse v2 cc2_0p2v=%d cc2_0p8v=%d cc2_2p6v=%d cc2_0p66v=%d cc2_1p23v=%d\n", cc2_0p2v, cc2_0p8v, cc2_2p6v, cc2_0p66v, cc2_1p23v); cc_param = &type_c_cfg->cc1_param; if (cc1_4p7k) cc_param->rp_4p7k_code = cc1_4p7k; if (cc1_12k) cc_param->rp_12k_code = cc1_12k; if (cc1_1p23v) cc_param->vref_1p23v = cc1_1p23v; if (cc1_0p66v) cc_param->vref_0p66v = cc1_0p66v; if (cc1_2p6v) cc_param->vref_2p6v = cc1_2p6v; if (cc1_0p8v) cc_param->vref_0p8v = cc1_0p8v; if (cc1_0p2v) cc_param->vref_0p2v = cc1_0p2v; cc_param = &type_c_cfg->cc2_param; if (cc2_4p7k) cc_param->rp_4p7k_code = cc2_4p7k; if (cc2_12k) cc_param->rp_12k_code = cc2_12k; if (cc2_1p23v) cc_param->vref_1p23v = cc2_1p23v; if (cc2_0p66v) cc_param->vref_0p66v = cc2_0p66v; if (cc2_2p6v) cc_param->vref_2p6v = cc2_2p6v; if (cc2_0p8v) cc_param->vref_0p8v = cc2_0p8v; if (cc2_0p2v) cc_param->vref_0p2v = cc2_0p2v; return 0; } static void get_default_type_c_parameter(struct type_c_data *type_c) { void __iomem *reg; int val; type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_3_0) | EN_RP4P7K; type_c->ufp_mode_rd_en = EN_RD; reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_0; val = readl(reg); type_c->cc1_code = CC_CODE_MASK & val; reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_0; val = readl(reg); type_c->cc2_code = CC_CODE_MASK & val; reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_1; val = readl(reg); type_c->cc1_vref = val; reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_1; val = readl(reg); type_c->cc2_vref = val; reg = type_c->reg_base + USB_TYPEC_CTRL; val = readl(reg); type_c->debounce = DEBOUNCE_TIME_MASK & val; } static int setup_type_c_parameter(struct type_c_data *type_c) { struct type_c_cfg *type_c_cfg = type_c->type_c_cfg; struct cc_param *cc_param; struct soc_device_attribute rtk_soc_efuse_v1[] = { { .family = "Realtek Phoenix",}, { .family = "Realtek Kylin",}, { .family = "Realtek Hercules",}, { .family = "Realtek Thor",}, { .family = "Realtek Hank",}, { .family = "Realtek Groot",}, { .family = "Realtek Stark",}, { .family = "Realtek Parker",}, { /* empty */ } }; if (type_c_cfg->use_defalut_parameter) { get_default_type_c_parameter(type_c); return 0; } if (soc_device_match(rtk_soc_efuse_v1)) __updated_type_c_parameter_by_efuse(type_c); else __updated_type_c_parameter_by_efuse_v2(type_c); /* * UFP rd vref_ufp : 1p23v, 0p66v, 0p2v * DFP_USB rp36k vref_dfp_usb: 0_1p6v, 0p2v, unused * DFP_1.5 rp12k vref_dfp_1_5: 1_1p6v, 0p4v, 0p2v * DFP_3.0 rp4p7k vref_dfp_3_0: 2p6v, 0p8v, 0p2v */ switch (type_c_cfg->cc_dfp_mode) { case CC_MODE_DFP_USB: type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_USB) | EN_RP36K; break; case CC_MODE_DFP_1_5: type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_1_5) | EN_RP12K; break; case CC_MODE_DFP_3_0: type_c->dfp_mode_rp_en = dfp_mode(CC_MODE_DFP_3_0) | EN_RP4P7K; break; default: dev_err(type_c->dev, "%s: unknown cc_dfp_mode %d\n", __func__, type_c_cfg->cc_dfp_mode); } type_c->ufp_mode_rd_en = EN_RD; cc_param = &type_c_cfg->cc1_param; type_c->cc1_code = rp4pk_code(cc_param->rp_4p7k_code) | rp36k_code(cc_param->rp_36k_code) | rp12k_code(cc_param->rp_12k_code) | rd_code(cc_param->rd_code); if (type_c_cfg->parameter_ver == PARAMETER_V0) type_c->cc1_vref = V0_vref_2p6v(cc_param->vref_2p6v) | V0_vref_1p23v(cc_param->vref_1p23v) | V0_vref_0p8v(cc_param->vref_0p8v) | V0_vref_0p66v(cc_param->vref_0p66v) | V0_vref_0p4v(cc_param->vref_0p4v) | V0_vref_0p2v(cc_param->vref_0p2v) | V0_vref_1_1p6v(cc_param->vref_1_1p6v) | V0_vref_0_1p6v(cc_param->vref_0_1p6v); else if (type_c_cfg->parameter_ver == PARAMETER_V1) type_c->cc1_vref = V1_vref_2p6v(cc_param->vref_2p6v) | V1_vref_1p23v(cc_param->vref_1p23v) | V1_vref_0p8v(cc_param->vref_0p8v) | V1_vref_0p66v(cc_param->vref_0p66v) | V1_vref_0p4v(cc_param->vref_0p4v) | V1_vref_0p2v(cc_param->vref_0p2v) | V1_vref_1_1p6v(cc_param->vref_1_1p6v) | V1_vref_0_1p6v(cc_param->vref_0_1p6v); else dev_err(type_c->dev, "%s: unknown parameter_ver %d\n", __func__, type_c_cfg->parameter_ver); cc_param = &type_c_cfg->cc2_param; type_c->cc2_code = rp4pk_code(cc_param->rp_4p7k_code) | rp36k_code(cc_param->rp_36k_code) | rp12k_code(cc_param->rp_12k_code) | rd_code(cc_param->rd_code); if (type_c_cfg->parameter_ver == PARAMETER_V0) type_c->cc2_vref = V0_vref_2p6v(cc_param->vref_2p6v) | V0_vref_1p23v(cc_param->vref_1p23v) | V0_vref_0p8v(cc_param->vref_0p8v) | V0_vref_0p66v(cc_param->vref_0p66v) | V0_vref_0p4v(cc_param->vref_0p4v) | V0_vref_0p2v(cc_param->vref_0p2v) | V0_vref_1_1p6v(cc_param->vref_1_1p6v) | V0_vref_0_1p6v(cc_param->vref_0_1p6v); else if (type_c_cfg->parameter_ver == PARAMETER_V1) type_c->cc2_vref = V1_vref_2p6v(cc_param->vref_2p6v) | V1_vref_1p23v(cc_param->vref_1p23v) | V1_vref_0p8v(cc_param->vref_0p8v) | V1_vref_0p66v(cc_param->vref_0p66v) | V1_vref_0p4v(cc_param->vref_0p4v) | V1_vref_0p2v(cc_param->vref_0p2v) | V1_vref_1_1p6v(cc_param->vref_1_1p6v) | V1_vref_0_1p6v(cc_param->vref_0_1p6v); else dev_err(type_c->dev, "%s: unknown parameter_ver %d\n", __func__, type_c_cfg->parameter_ver); type_c->debounce = (type_c_cfg->debounce_val << 1) | DEBOUNCE_EN; return 0; } static int extcon_rtk_type_c_init(struct type_c_data *type_c) { struct device *dev = type_c->dev; unsigned long flags; void __iomem *reg; int val; spin_lock_irqsave(&type_c->lock, flags); /* set parameter */ reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_0; val = readl(reg); val = (~CC_CODE_MASK & val) | (type_c->cc1_code & CC_CODE_MASK); writel(val, reg); reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_0; val = readl(reg); val = (~CC_CODE_MASK & val) | (type_c->cc2_code & CC_CODE_MASK); reg = type_c->reg_base + USB_TYPEC_CTRL_CC1_1; writel(type_c->cc1_vref, reg); reg = type_c->reg_base + USB_TYPEC_CTRL_CC2_1; writel(type_c->cc2_vref, reg); reg = type_c->reg_base + USB_TYPEC_CTRL; val = readl(reg); val = (~DEBOUNCE_TIME_MASK & val) | (type_c->debounce & DEBOUNCE_TIME_MASK); dev_info(dev, "First check USB_DR_MODE_PERIPHERAL"); type_c->cc_mode = IN_DEVICE_MODE; type_c->is_attach = IN_DETACH; type_c->connect_change = CONNECT_NO_CHANGE; detect_host(type_c); spin_unlock_irqrestore(&type_c->lock, flags); schedule_delayed_work(&type_c->delayed_work, msecs_to_jiffies(0)); if (!type_c->port) { struct typec_capability typec_cap = { }; struct fwnode_handle *fwnode; const char *buf; int ret; typec_cap.revision = USB_TYPEC_REV_1_0; typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; typec_cap.driver_data = type_c; typec_cap.ops = &type_c_port_ops; fwnode = device_get_named_child_node(dev, "connector"); if (!fwnode) return -EINVAL; ret = fwnode_property_read_string(fwnode, "power-role", &buf); if (ret) { dev_err(dev, "power-role not found: %d\n", ret); return ret; } ret = typec_find_port_power_role(buf); if (ret < 0) return ret; typec_cap.type = ret; ret = fwnode_property_read_string(fwnode, "data-role", &buf); if (ret) { dev_err(dev, "data-role not found: %d\n", ret); return ret; } ret = typec_find_port_data_role(buf); if (ret < 0) return ret; typec_cap.data = ret; type_c->port = typec_register_port(type_c->dev, &typec_cap); if (IS_ERR(type_c->port)) return PTR_ERR(type_c->port); } return 0; } static int extcon_rtk_type_c_edev_register(struct type_c_data *type_c) { struct device *dev = type_c->dev; int ret = 0; type_c->edev = devm_extcon_dev_allocate(dev, usb_type_c_cable); if (IS_ERR(type_c->edev)) { dev_err(dev, "failed to allocate extcon device\n"); return -ENOMEM; } ret = devm_extcon_dev_register(dev, type_c->edev); if (ret < 0) { dev_err(dev, "failed to register extcon device\n"); return ret; } extcon_set_property_capability(type_c->edev, EXTCON_USB, EXTCON_PROP_USB_VBUS); extcon_set_property_capability(type_c->edev, EXTCON_USB, EXTCON_PROP_USB_TYPEC_POLARITY); extcon_set_property_capability(type_c->edev, EXTCON_USB, EXTCON_PROP_USB_SS); extcon_set_property_capability(type_c->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_VBUS); extcon_set_property_capability(type_c->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_TYPEC_POLARITY); extcon_set_property_capability(type_c->edev, EXTCON_USB_HOST, EXTCON_PROP_USB_SS); return ret; } static int extcon_rtk_type_c_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct type_c_data *type_c; const struct type_c_cfg *type_c_cfg; int ret = 0; type_c = devm_kzalloc(dev, sizeof(*type_c), GFP_KERNEL); if (!type_c) return -ENOMEM; type_c->reg_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(type_c->reg_base)) return PTR_ERR(type_c->reg_base); type_c->dev = dev; type_c->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); if (type_c->irq <= 0) { dev_err(&pdev->dev, "Type C driver with no IRQ. Check %s setup!\n", dev_name(&pdev->dev)); ret = -ENODEV; goto err; } ret = devm_request_irq(dev, type_c->irq, type_c_detect_irq, IRQF_SHARED, "type_c_detect", type_c); spin_lock_init(&type_c->lock); type_c->rd_ctrl_gpio_desc = NULL; if (soc_device_match(rtk_soc_kylin)) { struct gpio_desc *gpio; gpio = fwnode_gpiod_get_index(of_fwnode_handle(dev->of_node), "realtek,rd-ctrl-gpios", 0, GPIOD_OUT_HIGH, "rd-ctrl-gpio"); if (IS_ERR(gpio)) { dev_err(dev, "Error rd_ctrl-gpios no found (err=%d)\n", (int)PTR_ERR(gpio)); } else { type_c->rd_ctrl_gpio_desc = gpio; dev_dbg(dev, "%s get rd-ctrl-gpios (id=%d) OK\n", __func__, desc_to_gpio(gpio)); } } type_c_cfg = of_device_get_match_data(dev); if (!type_c_cfg) { dev_err(dev, "type_c config are not assigned!\n"); ret = -EINVAL; goto err; } type_c->type_c_cfg = devm_kzalloc(dev, sizeof(*type_c_cfg), GFP_KERNEL); memcpy(type_c->type_c_cfg, type_c_cfg, sizeof(*type_c_cfg)); if (setup_type_c_parameter(type_c)) { dev_err(dev, "ERROR: %s to setup type c parameter!!", __func__); ret = -EINVAL; goto err; } INIT_DELAYED_WORK(&type_c->delayed_work, host_device_switch); ret = extcon_rtk_type_c_init(type_c); if (ret) { dev_err(dev, "%s failed to init type_c\n", __func__); goto err; } platform_set_drvdata(pdev, type_c); ret = extcon_rtk_type_c_edev_register(type_c); create_debug_files(type_c); return 0; err: dev_err(&pdev->dev, "%s: Probe fail, %d\n", __func__, ret); return ret; } static void extcon_rtk_type_c_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct type_c_data *type_c = dev_get_drvdata(dev); u32 default_ctrl; unsigned long flags; remove_debug_files(type_c); if (type_c->port) { typec_unregister_port(type_c->port); type_c->port = NULL; } cancel_delayed_work_sync(&type_c->delayed_work); flush_delayed_work(&type_c->delayed_work); WARN_ON_ONCE(delayed_work_pending(&type_c->delayed_work)); spin_lock_irqsave(&type_c->lock, flags); /* disable interrupt */ default_ctrl = readl(type_c->reg_base + USB_TYPEC_CTRL) & DEBOUNCE_TIME_MASK; writel(default_ctrl, type_c->reg_base + USB_TYPEC_CTRL); /* disable cc detect, rp, rd */ writel(PLR_EN, type_c->reg_base + USB_TYPEC_CTRL_CC1_0); writel(0, type_c->reg_base + USB_TYPEC_CTRL_CC2_0); spin_unlock_irqrestore(&type_c->lock, flags); if (type_c->rd_ctrl_gpio_desc) gpiod_put(type_c->rd_ctrl_gpio_desc); type_c->rd_ctrl_gpio_desc = NULL; free_irq(type_c->irq, type_c); } static const struct type_c_cfg rtd1295_type_c_cfg = { .parameter_ver = PARAMETER_V0, .cc_dfp_mode = CC_MODE_DFP_3_0, .cc1_param = { .rp_4p7k_code = 0xb, .rp_36k_code = 0x17, .rp_12k_code = 0x10, .rd_code = 0, .ra_code = 0, .vref_2p6v = 0x0, .vref_1p23v = 0x0, .vref_0p8v = 0x3, .vref_0p66v = 0x0, .vref_0p4v = 0x0, .vref_0p2v = 0x4, .vref_1_1p6v = 0, .vref_0_1p6v = 0 }, .cc2_param = { .rp_4p7k_code = 0xc, .rp_36k_code = 0x17, .rp_12k_code = 0x12, .rd_code = 0, .ra_code = 0, .vref_2p6v = 0x2, .vref_1p23v = 0x0, .vref_0p8v = 0x3, .vref_0p66v = 0x0, .vref_0p4v = 0x0, .vref_0p2v = 0x5, .vref_1_1p6v = 0, .vref_0_1p6v = 0 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct type_c_cfg rtd1395_type_c_cfg = { .parameter_ver = PARAMETER_V0, .cc_dfp_mode = CC_MODE_DFP_3_0, .cc1_param = { .rp_4p7k_code = 0xc, .rp_36k_code = 0xb, .rp_12k_code = 0xe, .rd_code = 0x10, .ra_code = 0x0, .vref_2p6v = 0x0, .vref_1p23v = 0x1, .vref_0p8v = 0x0, .vref_0p66v = 0x0, .vref_0p4v = 0x3, .vref_0p2v = 0x0, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .cc2_param = { .rp_4p7k_code = 0xb, .rp_36k_code = 0x9, .rp_12k_code = 0xe, .rd_code = 0xf, .ra_code = 0x0, .vref_2p6v = 0x1, .vref_1p23v = 0x3, .vref_0p8v = 0x3, .vref_0p66v = 0x2, .vref_0p4v = 0x3, .vref_0p2v = 0x2, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct type_c_cfg rtd1619_type_c_cfg = { .parameter_ver = PARAMETER_V0, .cc_dfp_mode = CC_MODE_DFP_3_0, .cc1_param = { .rp_4p7k_code = 0xc, .rp_36k_code = 0xf, .rp_12k_code = 0xe, .rd_code = 0x11, .ra_code = 0x0, .vref_2p6v = 0x5, .vref_1p23v = 0x7, .vref_0p8v = 0xa, .vref_0p66v = 0xa, .vref_0p4v = 0x3, .vref_0p2v = 0x2, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .cc2_param = { .rp_4p7k_code = 0xc, .rp_36k_code = 0xf, .rp_12k_code = 0xe, .rd_code = 0xf, .ra_code = 0x0, .vref_2p6v = 0x5, .vref_1p23v = 0x8, .vref_0p8v = 0xa, .vref_0p66v = 0xa, .vref_0p4v = 0x3, .vref_0p2v = 0x2, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct type_c_cfg rtd1319_type_c_cfg = { .parameter_ver = PARAMETER_V0, .cc_dfp_mode = CC_MODE_DFP_1_5, .cc1_param = { .rp_4p7k_code = 0x9, .rp_36k_code = 0xe, .rp_12k_code = 0x9, .rd_code = 0x9, .ra_code = 0x7, .vref_2p6v = 0x3, .vref_1p23v = 0x7, .vref_0p8v = 0x7, .vref_0p66v = 0x6, .vref_0p4v = 0x2, .vref_0p2v = 0x3, .vref_1_1p6v = 0x4, .vref_0_1p6v = 0x7 }, .cc2_param = { .rp_4p7k_code = 0x8, .rp_36k_code = 0xe, .rp_12k_code = 0x9, .rd_code = 0x9, .ra_code = 0x7, .vref_2p6v = 0x3, .vref_1p23v = 0x7, .vref_0p8v = 0x7, .vref_0p66v = 0x6, .vref_0p4v = 0x3, .vref_0p2v = 0x3, .vref_1_1p6v = 0x6, .vref_0_1p6v = 0x7 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct type_c_cfg rtd1312c_type_c_cfg = { .parameter_ver = PARAMETER_V0, .cc_dfp_mode = CC_MODE_DFP_1_5, .cc1_param = { .rp_4p7k_code = 0xe, .rp_36k_code = 0xc, .rp_12k_code = 0xc, .rd_code = 0xa, .ra_code = 0x3, .vref_2p6v = 0xa, .vref_1p23v = 0x7, .vref_0p8v = 0x7, .vref_0p66v = 0x7, .vref_0p4v = 0x4, .vref_0p2v = 0x4, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .cc2_param = { .rp_4p7k_code = 0xe, .rp_36k_code = 0xc, .rp_12k_code = 0xc, .rd_code = 0xa, .ra_code = 0x3, .vref_2p6v = 0xa, .vref_1p23v = 0x7, .vref_0p8v = 0x7, .vref_0p66v = 0x7, .vref_0p4v = 0x4, .vref_0p2v = 0x4, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct type_c_cfg rtd1619b_type_c_cfg = { .parameter_ver = PARAMETER_V1, .cc_dfp_mode = CC_MODE_DFP_1_5, .cc1_param = { .rp_4p7k_code = 0xf, .rp_36k_code = 0xf, .rp_12k_code = 0xf, .rd_code = 0xf, .ra_code = 0x7, .vref_2p6v = 0x9, .vref_1p23v = 0x7, .vref_0p8v = 0x9, .vref_0p66v = 0x8, .vref_0p4v = 0x7, .vref_0p2v = 0x9, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .cc2_param = { .rp_4p7k_code = 0xf, .rp_36k_code = 0xf, .rp_12k_code = 0xf, .rd_code = 0xf, .ra_code = 0x7, .vref_1p23v = 0x7, .vref_0p8v = 0x9, .vref_0p66v = 0x8, .vref_0p4v = 0x7, .vref_0p2v = 0x8, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct type_c_cfg rtd1319d_type_c_cfg = { .parameter_ver = PARAMETER_V1, .cc_dfp_mode = CC_MODE_DFP_1_5, .cc1_param = { .rp_4p7k_code = 0xe, .rp_36k_code = 0x3, .rp_12k_code = 0xe, .rd_code = 0xf, .ra_code = 0x6, .vref_2p6v = 0x7, .vref_1p23v = 0x7, .vref_0p8v = 0x8, .vref_0p66v = 0x7, .vref_0p4v = 0x7, .vref_0p2v = 0x7, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .cc2_param = { .rp_4p7k_code = 0xe, .rp_36k_code = 0x3, .rp_12k_code = 0xe, .rd_code = 0xf, .ra_code = 0x6, .vref_2p6v = 0x7, .vref_1p23v = 0x7, .vref_0p8v = 0x8, .vref_0p66v = 0x7, .vref_0p4v = 0x7, .vref_0p2v = 0x8, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct type_c_cfg rtd1315e_type_c_cfg = { .parameter_ver = PARAMETER_V1, .cc_dfp_mode = CC_MODE_DFP_1_5, .cc1_param = { .rp_4p7k_code = 0xe, .rp_36k_code = 0x3, .rp_12k_code = 0xe, .rd_code = 0xf, .ra_code = 0x6, .vref_2p6v = 0x7, .vref_1p23v = 0x7, .vref_0p8v = 0x8, .vref_0p66v = 0x7, .vref_0p4v = 0x7, .vref_0p2v = 0x7, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .cc2_param = { .rp_4p7k_code = 0xe, .rp_36k_code = 0x3, .rp_12k_code = 0xe, .rd_code = 0xf, .ra_code = 0x6, .vref_2p6v = 0x7, .vref_1p23v = 0x7, .vref_0p8v = 0x8, .vref_0p66v = 0x7, .vref_0p4v = 0x7, .vref_0p2v = 0x8, .vref_1_1p6v = 0x7, .vref_0_1p6v = 0x7 }, .debounce_val = 0x7f, /* 1b,1us 7f,4.7us */ .use_defalut_parameter = false, }; static const struct of_device_id extcon_rtk_type_c_match[] = { { .compatible = "realtek,rtd1295-type-c", .data = &rtd1295_type_c_cfg }, { .compatible = "realtek,rtd1312c-type-c", .data = &rtd1312c_type_c_cfg }, { .compatible = "realtek,rtd1315e-type-c", .data = &rtd1315e_type_c_cfg }, { .compatible = "realtek,rtd1319-type-c", .data = &rtd1319_type_c_cfg }, { .compatible = "realtek,rtd1319d-type-c", .data = &rtd1319d_type_c_cfg }, { .compatible = "realtek,rtd1395-type-c", .data = &rtd1395_type_c_cfg }, { .compatible = "realtek,rtd1619-type-c", .data = &rtd1619_type_c_cfg }, { .compatible = "realtek,rtd1619b-type-c", .data = &rtd1619b_type_c_cfg }, {}, }; MODULE_DEVICE_TABLE(of, extcon_rtk_type_c_match); #ifdef CONFIG_PM_SLEEP static int extcon_rtk_type_c_prepare(struct device *dev) { struct type_c_data *type_c = dev_get_drvdata(dev); u32 default_ctrl; unsigned long flags; cancel_delayed_work_sync(&type_c->delayed_work); flush_delayed_work(&type_c->delayed_work); WARN_ON_ONCE(delayed_work_pending(&type_c->delayed_work)); spin_lock_irqsave(&type_c->lock, flags); /* disable interrupt */ default_ctrl = readl(type_c->reg_base + USB_TYPEC_CTRL) & DEBOUNCE_TIME_MASK; writel(default_ctrl, type_c->reg_base + USB_TYPEC_CTRL); /* disable cc detect, rp, rd */ writel(PLR_EN, type_c->reg_base + USB_TYPEC_CTRL_CC1_0); writel(0, type_c->reg_base + USB_TYPEC_CTRL_CC2_0); spin_unlock_irqrestore(&type_c->lock, flags); return 0; } static void extcon_rtk_type_c_complete(struct device *dev) { /* nothing */ } static int extcon_rtk_type_c_suspend(struct device *dev) { /* nothing */ return 0; } static int extcon_rtk_type_c_resume(struct device *dev) { struct type_c_data *type_c = dev_get_drvdata(dev); int ret; ret = extcon_rtk_type_c_init(type_c); if (ret) { dev_err(dev, "%s failed to init type_c\n", __func__); return ret; } return 0; } static const struct dev_pm_ops extcon_rtk_type_c_pm_ops = { SET_LATE_SYSTEM_SLEEP_PM_OPS(extcon_rtk_type_c_suspend, extcon_rtk_type_c_resume) .prepare = extcon_rtk_type_c_prepare, .complete = extcon_rtk_type_c_complete, }; #define DEV_PM_OPS (&extcon_rtk_type_c_pm_ops) #else #define DEV_PM_OPS NULL #endif /* CONFIG_PM_SLEEP */ static struct platform_driver extcon_rtk_type_c_driver = { .probe = extcon_rtk_type_c_probe, .remove_new = extcon_rtk_type_c_remove, .driver = { .name = "extcon-rtk-type_c", .of_match_table = extcon_rtk_type_c_match, .pm = DEV_PM_OPS, }, }; module_platform_driver(extcon_rtk_type_c_driver); MODULE_DESCRIPTION("Realtek Extcon Type C driver"); MODULE_AUTHOR("Stanley Chang "); MODULE_LICENSE("GPL");