From: Chandra Sekhar Anagani <chandra.sekhar.anagani@xxxxxxxxx> This adds PD sink port support for the USB Type-C PHY on Intel WhiskeyCove PMIC which is available on some of the Intel Broxton SoC based platforms. This patch depends on these two patches: https://lkml.org/lkml/2016/6/29/349 https://lkml.org/lkml/2016/6/29/350 Signed-off-by: Chandra Sekhar Anagani <chandra.sekhar.anagani@xxxxxxxxx> Signed-off-by: Pranav Tipnis <pranav.tipnis@xxxxxxxxx> Signed-off-by: Bin Gao <bin.gao@xxxxxxxxx> Changes in v2: - Added PD support for cold boot case --- drivers/usb/typec/typec_wcove.c | 309 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 285 insertions(+), 24 deletions(-) diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c index c7c2d28..000d6ae 100644 --- a/drivers/usb/typec/typec_wcove.c +++ b/drivers/usb/typec/typec_wcove.c @@ -3,6 +3,7 @@ * * Copyright (C) 2016 Intel Corporation * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> + * Author: Chandra Sekhar Anagani <chandra.sekhar.anagani@xxxxxxxxx> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -10,9 +11,11 @@ */ #include <linux/acpi.h> +#include <linux/delay.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/usb/typec.h> +#include <linux/usb/pd_sink.h> #include <linux/platform_device.h> #include <linux/mfd/intel_soc_pmic.h> @@ -25,6 +28,7 @@ #define USBC_CONTROL3 0x7003 #define USBC_CC1_CTRL 0x7004 #define USBC_CC2_CTRL 0x7005 +#define USBC_CC_SEL 0x7006 #define USBC_STATUS1 0x7007 #define USBC_STATUS2 0x7008 #define USBC_STATUS3 0x7009 @@ -32,7 +36,16 @@ #define USBC_IRQ2 0x7016 #define USBC_IRQMASK1 0x7017 #define USBC_IRQMASK2 0x7018 - +#define USBC_PD_CFG1 0x7019 +#define USBC_PD_CFG2 0x701a +#define USBC_PD_CFG3 0x701b +#define USBC_PD_STATUS 0x701c +#define USBC_RX_STATUS 0x701d +#define USBC_RX_INFO 0x701e +#define USBC_TX_CMD 0x701f +#define USBC_TX_INFO 0x7020 +#define USBC_RX_DATA_START 0x7028 +#define USBC_TX_DATA_START 0x7047 /* Register bits */ #define USBC_CONTROL1_MODE_DRP(r) ((r & ~0x7) | 4) @@ -44,7 +57,9 @@ #define USBC_CONTROL3_PD_DIS BIT(1) #define USBC_CC_CTRL_VCONN_EN BIT(1) +#define USBC_CC_CTRL_TX_EN BIT(2) +#define USBC_CC_SEL_CCSEL (BIT(0) | BIT(1)) #define USBC_STATUS1_DET_ONGOING BIT(6) #define USBC_STATUS1_RSLT(r) (r & 0xf) #define USBC_RSLT_NOTHING 0 @@ -79,11 +94,44 @@ USBC_IRQ2_RX_HR | USBC_IRQ2_RX_CR | \ USBC_IRQ2_TX_SUCCESS | USBC_IRQ2_TX_FAIL) +#define USBC_PD_CFG1_ID_FILL BIT(7) + +#define USBC_PD_CFG2_SOP_RX BIT(0) + +#define USBC_PD_CFG3_SR_SOP2 (BIT(7) | BIT(6)) +#define USBC_PD_CFG3_SR_SOP1 (BIT(5) | BIT(4)) +#define USBC_PD_CFG3_SR_SOP0 (BIT(3) | BIT(2)) +#define USBC_PD_CFG3_DATAROLE BIT(1) +#define USBC_PD_CFG3_PWRROLE BIT(0) + +#define USBC_TX_CMD_TXBUF_RDY BIT(0) +#define USBC_TX_CMD_TX_START BIT(1) +#define USBC_TX_CMD_TXBUF_CMD(r) ((r >> 5) & 0x7) + +#define USBC_TX_INFO_TX_SOP (BIT(0) | BIT(1) | BIT(2)) +#define USBC_TX_INFO_TX_RETRIES (BIT(3) | BIT(4) | BIT(5)) + +#define USBC_RX_STATUS_RX_DATA BIT(7) +#define USBC_RX_STATUS_RX_OVERRUN BIT(6) +#define USBC_RX_STATUS_RX_CLEAR BIT(0) + +#define USBC_PD_STATUS_RX_RSLT(r) ((r >> 3) & 0x7) +#define USBC_PD_STATUS_TX_RSLT(r) (r & 0x7) + +#define USBC_RX_INFO_RXBYTES(r) ((r >> 3) & 0x1f) +#define USBC_RX_INFO_RX_SOP(r) (r & 0x7) + +#define USBC_PD_RX_BUF_LEN 30 +#define USBC_PD_TX_BUF_LEN 30 +#define USBC_PD_SEND_HR (3 << 5) + struct wcove_typec { + int pd_port_num; struct mutex lock; /* device lock */ struct device *dev; struct regmap *regmap; struct typec_port *port; + struct pd_sink_port pd_port; struct typec_capability cap; struct typec_connection con; struct typec_partner partner; @@ -106,6 +154,50 @@ enum wcove_typec_role { WCOVE_ROLE_DEVICE, }; +static struct sink_ps profiles[] = { + + { + .ps_type = PS_TYPE_FIXED, + .ps_fixed = { + .voltage_fixed = 100, /* 5V/50mV = 100 */ + .current_default = 90, /* 900mA/10mA = 90 */ + .current_max = 90, /* 900mA/10mA = 90 */ + }, + + }, + + { + .ps_type = PS_TYPE_FIXED, + .ps_fixed = { + .voltage_fixed = 100, + .current_default = 300, + .current_max = 300, + }, + }, + + { + .ps_type = PS_TYPE_FIXED, + .ps_fixed = { + .voltage_fixed = 240, + .current_default = 300, + .current_max = 300, + }, + }, + +}; + +static struct pd_sink_profile profile = { + .hw_goodcrc_tx = true, + .hw_goodcrc_rx = true, + .gotomin = true, + .usb_comm = true, + .spec = USB_SPEC_3X, + .pd_rev = PD_REVISION_2, + .ps = profiles, + .nr_ps = ARRAY_SIZE(profiles), + .active_ps = 2, /* voltage = 5V, current = 3A */ +}; + static uuid_le uuid = UUID_LE(0x482383f0, 0x2876, 0x4e49, 0x86, 0x85, 0xdb, 0x66, 0x21, 0x1a, 0xf0, 0x37); @@ -140,7 +232,102 @@ static void wcove_typec_device_mode(struct wcove_typec *wcove) typec_connect(wcove->port, &wcove->con); } -static irqreturn_t wcove_typec_irq(int irq, void *data) +static int wcove_typec_pd_recv_pkt_handler(struct wcove_typec *wcove) +{ + unsigned int rx_status; + unsigned int rx_info; + unsigned int temp; + int len; + int ret, i; + char buf[USBC_PD_RX_BUF_LEN]; + + ret = regmap_read(wcove->regmap, USBC_RX_STATUS, &rx_status); + if (ret) + goto err; + + while (rx_status & USBC_RX_STATUS_RX_DATA) { + ret = regmap_read(wcove->regmap, USBC_RX_INFO, &rx_info); + if (ret) + goto err; + + len = (USBC_RX_INFO_RXBYTES(rx_info)); + + for (i = 0; i < len; i++) { + ret = regmap_read(wcove->regmap, USBC_RX_DATA_START + i, + &temp); + buf[i] = (char)temp; + if (ret) + goto err; + } + + ret = pd_sink_handle_msg(wcove->pd_port_num, buf, len); + if (ret) { + dev_err(wcove->dev, "pd_sink_handle_msg() failed: %d\n", + ret); + /* Clear RX status */ + regmap_update_bits(wcove->regmap, USBC_RX_STATUS, + USBC_RX_STATUS_RX_CLEAR, + USBC_RX_STATUS_RX_CLEAR); + goto err; + } + + /* Clear RX status */ + regmap_update_bits(wcove->regmap, USBC_RX_STATUS, + USBC_RX_STATUS_RX_CLEAR, USBC_RX_STATUS_RX_CLEAR); + + ret = regmap_read(wcove->regmap, USBC_RX_STATUS, &rx_status); + if (ret) + goto err; + } + + return 0; + +err: + return ret; +} + +static int wcove_typec_pd_tx_pkt_handler(int port_num, void *data, + void *buf, int len, enum sop_type pkt_type) +{ + unsigned int tx_cmd; + unsigned int val; + int ret, i; + char *buf1 = buf; + struct wcove_typec *wcove = data; + + ret = regmap_read(wcove->regmap, USBC_TX_CMD, &tx_cmd); + if (ret) + goto err; + + if (!(tx_cmd & USBC_TX_CMD_TXBUF_RDY)) + return -EBUSY; + + for (i = 0; i < len; i++) + ret = regmap_write(wcove->regmap, USBC_TX_DATA_START + i, + buf1[i]); + if (ret) + goto err; + + regmap_read(wcove->regmap, USBC_TX_INFO, &val); + ret = regmap_write(wcove->regmap, USBC_TX_INFO, 0x71); + if (ret) + goto err; + + ret = regmap_write(wcove->regmap, USBC_TX_CMD, + USBC_TX_CMD_TX_START | (1 << 5)); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_TX_CMD, &tx_cmd); + if (ret) + goto err; + +err: + kfree(buf1); + return ret; +} + +static irqreturn_t wcove_typec_irq(int irq, void *data) { struct wcove_typec *wcove = data; unsigned int cc1_ctrl; @@ -149,10 +336,10 @@ static irqreturn_t wcove_typec_irq(int irq, void *data) unsigned int cc_irq2; unsigned int status1; unsigned int status2; + unsigned int rx_status; int ret; mutex_lock(&wcove->lock); - ret = regmap_read(wcove->regmap, USBC_IRQ1, &cc_irq1); if (ret) goto err; @@ -177,6 +364,10 @@ static irqreturn_t wcove_typec_irq(int irq, void *data) if (ret) goto err; + ret = regmap_read(wcove->regmap, USBC_RX_STATUS, &rx_status); + if (ret) + goto err; + if (cc_irq1) { if (cc_irq1 & USBC_IRQ1_OVERTEMP) dev_err(wcove->dev, "VCONN Switch Over Temperature!\n"); @@ -185,17 +376,9 @@ static irqreturn_t wcove_typec_irq(int irq, void *data) regmap_write(wcove->regmap, USBC_IRQ1, cc_irq1); } - if (cc_irq2) { - regmap_write(wcove->regmap, USBC_IRQ2, cc_irq2); - /* - * Ingoring any PD communication interrupts until the PD stack - * is in place - */ - if (cc_irq2 & ~USBC_IRQ2_CC_CHANGE) { - dev_WARN(wcove->dev, "USB PD handling missing\n"); - goto err; - } - } + if (cc_irq2 & USBC_IRQ2_RX_PD || + rx_status & USBC_RX_STATUS_RX_DATA) + wcove_typec_pd_recv_pkt_handler(wcove); if (status1 & USBC_STATUS1_DET_ONGOING) goto out; @@ -211,24 +394,49 @@ static irqreturn_t wcove_typec_irq(int irq, void *data) WCOVE_ORIENTATION_NORMAL); /* Host mode by default */ wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_HOST); - goto out; - } - if (wcove->con.partner) + /* reset the pd sink state */ + if (wcove->pd_port_num >= 0) + pd_sink_reset_state(wcove->pd_port_num); + goto out; + } switch (USBC_STATUS1_ORIENT(status1)) { case USBC_ORIENT_NORMAL: wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, WCOVE_ORIENTATION_NORMAL); + regmap_update_bits(wcove->regmap, USBC_CC_SEL, + USBC_CC_SEL_CCSEL, 0x1); break; case USBC_ORIENT_REVERSE: wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, WCOVE_ORIENTATION_REVERSE); + regmap_update_bits(wcove->regmap, USBC_CC_SEL, + USBC_CC_SEL_CCSEL, 0x2); default: break; } + if (cc_irq2 & USBC_IRQ2_RX_HR) { + dev_dbg(wcove->dev, "Hard Reset received\n"); + if (wcove->pd_port_num >= 0) + pd_sink_reset_state(wcove->pd_port_num); + + } + + if (cc_irq2 & USBC_IRQ2_RX_CR) + dev_info(wcove->dev, "RX CR not implemented\n"); + + if (cc_irq2 & USBC_IRQ2_TX_SUCCESS) + pd_sink_tx_complete(wcove->pd_port_num); + + if (cc_irq2 & USBC_IRQ2_TX_FAIL) + dev_warn(wcove->dev, "TX_FAIL\n"); + + if (wcove->con.partner) + goto out; + switch (USBC_STATUS1_RSLT(status1)) { case USBC_RSLT_SRC_DEFAULT: wcove->con.pwr_opmode = TYPEC_PWR_MODE_USB; @@ -280,7 +488,10 @@ out: !!(status2 & USBC_STATUS2_VBUS_REQ)); err: /* REVISIT: Clear WhiskeyCove CHGR Type-C interrupt */ - regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5)); + regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5) | BIT(4) | + BIT(3) | BIT(0)); + regmap_write(wcove->regmap, USBC_IRQ1, cc_irq1); + regmap_write(wcove->regmap, USBC_IRQ2, cc_irq2); mutex_unlock(&wcove->lock); return IRQ_HANDLED; @@ -291,6 +502,8 @@ static int wcove_typec_probe(struct platform_device *pdev) struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); struct wcove_typec *wcove; unsigned int val; + unsigned int status1; + unsigned int tx_cmd; int ret; wcove = devm_kzalloc(&pdev->dev, sizeof(*wcove), GFP_KERNEL); @@ -307,26 +520,29 @@ static int wcove_typec_probe(struct platform_device *pdev) return ret; ret = devm_request_threaded_irq(&pdev->dev, ret, NULL, - wcove_typec_irq, IRQF_ONESHOT, + wcove_typec_irq, IRQF_ONESHOT, "wcove_typec", wcove); if (ret) return ret; wcove->cap.type = TYPEC_PORT_DRP; - wcove->port = typec_register_port(&pdev->dev, &wcove->cap); if (IS_ERR(wcove->port)) return PTR_ERR(wcove->port); + /* PD receive packet handler */ + wcove->pd_port_num = pd_sink_register_port(&profile, + wcove_typec_pd_tx_pkt_handler, wcove); + if (wcove->pd_port_num) { + dev_err(&pdev->dev, "Register pd sink port failed\n"); + return -EIO; + } + if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), uuid.b, 0, 0x1f)) { dev_err(&pdev->dev, "Missing _DSM functions\n"); return -ENODEV; } - /* Make sure the PD PHY is disabled until PD stack is ready */ - regmap_read(wcove->regmap, USBC_CONTROL3, &val); - regmap_write(wcove->regmap, USBC_CONTROL3, val | USBC_CONTROL3_PD_DIS); - /* DRP mode without accessory support */ regmap_read(wcove->regmap, USBC_CONTROL1, &val); regmap_write(wcove->regmap, USBC_CONTROL1, USBC_CONTROL1_MODE_DRP(val)); @@ -337,7 +553,50 @@ static int wcove_typec_probe(struct platform_device *pdev) regmap_read(wcove->regmap, USBC_IRQMASK2, &val); regmap_write(wcove->regmap, USBC_IRQMASK2, val & ~USBC_IRQMASK2_ALL); + /*Set HW control the ID of outgoing messages*/ + regmap_write(wcove->regmap, USBC_PD_CFG1, BIT(7)); + + /* Enable SOP messages for now */ + regmap_write(wcove->regmap, USBC_PD_CFG2, BIT(0)); + + /*Set the PD revision */ + regmap_read(wcove->regmap, USBC_PD_CFG3, &val); + val = 0x14; + regmap_write(wcove->regmap, USBC_PD_CFG3, val); + platform_set_drvdata(pdev, wcove); + + ret = regmap_read(wcove->regmap, USBC_STATUS1, &status1); + if (ret) + return ret; + + /* If cable is connected while booting, we would have lost all + * SOURCE_CAP messages. Hence, we send Hard Reset to source so + * that source would send us SOURCE_CAP messages again and we + * can establish PD communication again. + */ + if (USBC_STATUS1_ORIENT(status1) == USBC_ORIENT_NORMAL || + USBC_STATUS1_ORIENT(status1) == USBC_ORIENT_REVERSE) { + + /* Read TX_CMD register */ + ret = regmap_read(wcove->regmap, USBC_TX_CMD, &tx_cmd); + if (ret) + return ret; + + /* Send Hard Reset */ + ret = regmap_write(wcove->regmap, USBC_TX_CMD, tx_cmd | + USBC_PD_SEND_HR); + if (ret) + return ret; + + /* Start Transmission */ + ret = regmap_update_bits(wcove->regmap, USBC_TX_CMD, + USBC_TX_CMD_TX_START, + USBC_TX_CMD_TX_START); + if (ret) + return ret; + } + return 0; } @@ -353,6 +612,7 @@ static int wcove_typec_remove(struct platform_device *pdev) regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL); typec_unregister_port(wcove->port); + pd_sink_unregister_port(wcove->pd_port_num); return 0; } @@ -366,6 +626,7 @@ static struct platform_driver wcove_typec_driver = { module_platform_driver(wcove_typec_driver); +MODULE_ALIAS("platform:bxt_wcove_usbc"); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("WhiskeyCove PMIC USB Type-C PHY driver"); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html