diff --git a/Documentation/devicetree/bindings/usb/qpnp-pdphy.txt b/Documentation/devicetree/bindings/usb/qpnp-pdphy.txt new file mode 100644 index 000000000000..f5c6651affea --- /dev/null +++ b/Documentation/devicetree/bindings/usb/qpnp-pdphy.txt @@ -0,0 +1,64 @@ +Qualcomm QPNP PD PHY - USB Power Delivery Physical layer + +Required properties: +- compatible: Must be "qcom,qpnp-pdphy" +- reg: The base address for this peripheral +- vdd-pdphy-supply: phandle to the VDD supply regulator node +- interrupts: Specifies the interrupt associated with the peripheral. +- interrupt-names: Specifies the interrupt names for the peripheral. Every + available interrupt needs to have an associated name + with it to indentify its purpose. + + The following interrupts are required: + + 0: sig-tx + Triggers when a signal (HardReset or CableReset) + has been sent. + 1: sig-rx + Triggers when a signal has been received. + 2: msg-tx + Triggers when a message has been sent and the + related GoodCRC has been received. + 3: msg-rx + Triggers when a message has been received and + the related GoodCRC was sent successfully. + 4: msg-tx-failed + Triggers when a message failed all its + transmission attempts, either due to a non-idle + bus or missing GoodCRC reply. + 5: msg-tx-discarded + Triggers when a message is received while a + transmission request was in place. The request + itself is discarded. + 6: msg-rx-discarded + Triggers when a message was received but had to + be discarded due to the RX buffer still in use + by SW. + +Optional properties: +- vbus-supply: Regulator that enables VBUS source output +- vconn-supply: Regulator that enables VCONN source output. This will + be supplied on the USB CC line that is not used for + communication when Ra resistance is detected. + +Example: + qcom,qpnp-pdphy@1700 { + compatible = "qcom,qpnp-pdphy"; + reg = <0x1700 0x100>; + vdd-pdphy-supply = <&pmcobalt_l24>; + interrupts = <0x2 0x17 0x0 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x1 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x2 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x3 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x4 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x5 IRQ_TYPE_EDGE_RISING>, + <0x2 0x17 0x6 IRQ_TYPE_EDGE_RISING>; + + interrupt-names = "sig-tx", + "sig-rx", + "msg-tx", + "msg-rx", + "msg-tx-failed", + "msg-tx-discarded", + "msg-rx-discarded"; + }; diff --git a/drivers/usb/pd/Kconfig b/drivers/usb/pd/Kconfig index fe61504a22e2..dd2813000dec 100644 --- a/drivers/usb/pd/Kconfig +++ b/drivers/usb/pd/Kconfig @@ -13,4 +13,12 @@ config USB_PD_POLICY help Say Y here to enable USB PD protocol and policy engine. +config QPNP_USB_PDPHY + tristate "QPNP USB Power Delivery PHY" + depends on SPMI + help + Say Y here to enable QPNP USB PD PHY peripheral driver + which communicates over the SPMI bus. The is used to handle + the PHY layer communication of the Power Delivery stack. + endmenu diff --git a/drivers/usb/pd/Makefile b/drivers/usb/pd/Makefile index 4d9f20598751..f48707026799 100644 --- a/drivers/usb/pd/Makefile +++ b/drivers/usb/pd/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_USB_PD_POLICY) += policy_engine.o +obj-$(CONFIG_QPNP_USB_PDPHY) += qpnp-pdphy.o diff --git a/drivers/usb/pd/qpnp-pdphy.c b/drivers/usb/pd/qpnp-pdphy.c new file mode 100644 index 000000000000..4585393a9552 --- /dev/null +++ b/drivers/usb/pd/qpnp-pdphy.c @@ -0,0 +1,829 @@ +/* Copyright (c) 2016, Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usbpd.h" + +#define USB_PDPHY_MAX_DATA_OBJ_LEN 28 +#define USB_PDPHY_MSG_HDR_LEN 2 + +/* PD PHY register offsets and bit fields */ +#define USB_PDPHY_MSG_CONFIG 0x40 +#define MSG_CONFIG_PORT_DATA_ROLE BIT(3) +#define MSG_CONFIG_PORT_POWER_ROLE BIT(2) + +#define USB_PDPHY_EN_CONTROL 0x46 +#define CONTROL_ENABLE BIT(0) + +#define USB_PDPHY_RX_STATUS 0x4A +#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2)) + +#define USB_PDPHY_FRAME_FILTER 0x4C +#define FRAME_FILTER_EN_HARD_RESET BIT(5) +#define FRAME_FILTER_EN_SOP BIT(0) + +#define USB_PDPHY_TX_SIZE 0x42 +#define TX_SIZE_MASK 0xF + +#define USB_PDPHY_TX_CONTROL 0x44 +#define TX_CONTROL_RETRY_COUNT (BIT(6) | BIT(5)) +#define TX_CONTROL_FRAME_TYPE (BIT(4) | BIT(3) | BIT(2)) +#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2) +#define TX_CONTROL_SEND_SIGNAL BIT(1) +#define TX_CONTROL_SEND_MSG BIT(0) + +#define USB_PDPHY_RX_SIZE 0x48 + +#define USB_PDPHY_RX_ACKNOWLEDGE 0x4B +#define RX_BUFFER_TOKEN BIT(0) + +#define USB_PDPHY_TX_BUFFER_HDR 0x60 +#define USB_PDPHY_TX_BUFFER_DATA 0x62 + +#define USB_PDPHY_RX_BUFFER 0x80 + +/* VDD regulator */ +#define VDD_PDPHY_VOL_MIN 3088000 /* uV */ +#define VDD_PDPHY_VOL_MAX 3088000 /* uV */ +#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ + +struct usb_pdphy { + struct device *dev; + struct regmap *regmap; + + u16 base; + struct regulator *vdd_pdphy; + + /* irqs */ + int sig_tx_irq; + int sig_rx_irq; + int msg_tx_irq; + int msg_rx_irq; + int msg_tx_failed_irq; + int msg_tx_discarded_irq; + int msg_rx_discarded_irq; + + void (*signal_cb)(struct usbpd *pd, enum pd_sig_type type); + void (*msg_rx_cb)(struct usbpd *pd, enum pd_msg_type type, + u8 *buf, size_t len); + void (*shutdown_cb)(struct usbpd *pd); + + /* write waitq */ + wait_queue_head_t tx_waitq; + + bool is_opened; + int tx_status; + u8 frame_filter_val; + + enum data_role data_role; + enum power_role power_role; + + struct usbpd *usbpd; + + /* debug */ + struct dentry *debug_root; + unsigned int tx_bytes; /* hdr + data */ + unsigned int rx_bytes; /* hdr + data */ + unsigned int sig_tx_cnt; + unsigned int sig_rx_cnt; + unsigned int msg_tx_cnt; + unsigned int msg_rx_cnt; + unsigned int msg_tx_failed_cnt; + unsigned int msg_tx_discarded_cnt; + unsigned int msg_rx_discarded_cnt; +}; + +static struct usb_pdphy *__pdphy; + +static int pdphy_dbg_status(struct seq_file *s, void *p) +{ + struct usb_pdphy *pdphy = s->private; + + seq_printf(s, + "PD Phy driver status\n" + "==================================================\n"); + seq_printf(s, "opened: %10d\n", pdphy->is_opened); + seq_printf(s, "tx status: %10d\n", pdphy->tx_status); + seq_printf(s, "tx bytes: %10u\n", pdphy->tx_bytes); + seq_printf(s, "rx bytes: %10u\n", pdphy->rx_bytes); + seq_printf(s, "data role: %10u\n", pdphy->data_role); + seq_printf(s, "power role: %10u\n", pdphy->power_role); + seq_printf(s, "frame filter: %10u\n", pdphy->frame_filter_val); + seq_printf(s, "sig tx cnt: %10u\n", pdphy->sig_tx_cnt); + seq_printf(s, "sig rx cnt: %10u\n", pdphy->sig_rx_cnt); + seq_printf(s, "msg tx cnt: %10u\n", pdphy->msg_tx_cnt); + seq_printf(s, "msg rx cnt: %10u\n", pdphy->msg_rx_cnt); + seq_printf(s, "msg tx failed cnt: %10u\n", + pdphy->msg_tx_failed_cnt); + seq_printf(s, "msg tx discarded cnt: %10u\n", + pdphy->msg_tx_discarded_cnt); + seq_printf(s, "msg rx discarded cnt: %10u\n", + pdphy->msg_rx_discarded_cnt); + + return 0; +} + +static int pdphy_dbg_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, pdphy_dbg_status, inode->i_private); +} + +static const struct file_operations status_ops = { + .owner = THIS_MODULE, + .open = pdphy_dbg_status_open, + .llseek = seq_lseek, + .read = seq_read, + .release = single_release, +}; + +static void pdphy_create_debugfs_entries(struct usb_pdphy *pdphy) +{ + struct dentry *ent; + + pdphy->debug_root = debugfs_create_dir("usb-pdphy", NULL); + if (!pdphy->debug_root) { + dev_warn(pdphy->dev, "Couldn't create debug dir\n"); + return; + } + + ent = debugfs_create_file("status", S_IRUSR, pdphy->debug_root, pdphy, + &status_ops); + if (!ent) { + dev_warn(pdphy->dev, "Couldn't create status file\n"); + debugfs_remove(pdphy->debug_root); + } +} + +static int pdphy_enable_power(struct usb_pdphy *pdphy, bool on) +{ + int ret = 0; + + dev_dbg(pdphy->dev, "%s turn %s regulator.\n", __func__, + on ? "on" : "off"); + + if (!on) + goto disable_pdphy_vdd; + + ret = regulator_set_load(pdphy->vdd_pdphy, VDD_PDPHY_HPM_LOAD); + if (ret < 0) { + dev_err(pdphy->dev, "Unable to set HPM of vdd_pdphy:%d\n", ret); + return ret; + } + + ret = regulator_set_voltage(pdphy->vdd_pdphy, VDD_PDPHY_VOL_MIN, + VDD_PDPHY_VOL_MAX); + if (ret) { + dev_err(pdphy->dev, + "set voltage failed for vdd_pdphy:%d\n", ret); + goto put_pdphy_vdd_lpm; + } + + ret = regulator_enable(pdphy->vdd_pdphy); + if (ret) { + dev_err(pdphy->dev, "Unable to enable vdd_pdphy:%d\n", ret); + goto unset_pdphy_vdd; + } + + dev_dbg(pdphy->dev, "%s: PD PHY regulator turned ON.\n", __func__); + return ret; + +disable_pdphy_vdd: + ret = regulator_disable(pdphy->vdd_pdphy); + if (ret) + dev_err(pdphy->dev, "Unable to disable vdd_pdphy:%d\n", ret); + +unset_pdphy_vdd: + ret = regulator_set_voltage(pdphy->vdd_pdphy, 0, VDD_PDPHY_VOL_MAX); + if (ret) + dev_err(pdphy->dev, + "Unable to set (0) voltage for vdd_pdphy:%d\n", ret); + +put_pdphy_vdd_lpm: + ret = regulator_set_load(pdphy->vdd_pdphy, 0); + if (ret < 0) + dev_err(pdphy->dev, "Unable to set (0) HPM of vdd_pdphy\n"); + + return ret; +} + +void pdphy_enable_irq(struct usb_pdphy *pdphy, bool enable) +{ + if (enable) { + enable_irq(pdphy->sig_tx_irq); + enable_irq(pdphy->sig_rx_irq); + enable_irq(pdphy->msg_tx_irq); + enable_irq(pdphy->msg_rx_irq); + enable_irq(pdphy->msg_tx_failed_irq); + enable_irq(pdphy->msg_tx_discarded_irq); + enable_irq(pdphy->msg_rx_discarded_irq); + return; + } + + disable_irq(pdphy->sig_tx_irq); + disable_irq(pdphy->sig_rx_irq); + disable_irq(pdphy->msg_tx_irq); + disable_irq(pdphy->msg_rx_irq); + disable_irq(pdphy->msg_tx_failed_irq); + disable_irq(pdphy->msg_tx_discarded_irq); + disable_irq(pdphy->msg_rx_discarded_irq); +} + +static int pdphy_reg_read(struct usb_pdphy *pdphy, u8 *val, u16 addr, int count) +{ + int ret; + + ret = regmap_bulk_read(pdphy->regmap, pdphy->base + addr, val, count); + if (ret) { + dev_err(pdphy->dev, "read failed: addr=0x%04x, ret=%d\n", + pdphy->base + addr, ret); + return ret; + } + + return 0; +} + +/* Write multiple registers to device with block of data */ +static int pdphy_bulk_reg_write(struct usb_pdphy *pdphy, u16 addr, + const void *val, u8 val_cnt) +{ + int ret; + + ret = regmap_bulk_write(pdphy->regmap, pdphy->base + addr, + val, val_cnt); + if (ret) { + dev_err(pdphy->dev, "bulk write failed: addr=0x%04x, ret=%d\n", + pdphy->base + addr, ret); + return ret; + } + + return 0; +} + +/* Writes a single byte to the specified register */ +static inline int pdphy_reg_write(struct usb_pdphy *pdphy, u16 addr, u8 val) +{ + return pdphy_bulk_reg_write(pdphy, addr, &val, 1); +} + +/* Writes to the specified register limited by the bit mask */ +static int pdphy_masked_write(struct usb_pdphy *pdphy, u16 addr, + u8 mask, u8 val) +{ + int ret; + + ret = regmap_update_bits(pdphy->regmap, pdphy->base + addr, mask, val); + if (ret) { + dev_err(pdphy->dev, "write failed: addr=0x%04x, ret=%d\n", + pdphy->base + addr, ret); + return ret; + } + + return 0; +} + +int pd_phy_update_roles(enum data_role dr, enum power_role pr) +{ + struct usb_pdphy *pdphy = __pdphy; + + return pdphy_masked_write(pdphy, USB_PDPHY_MSG_CONFIG, + (MSG_CONFIG_PORT_DATA_ROLE | MSG_CONFIG_PORT_POWER_ROLE), + ((dr == DR_DFP ? MSG_CONFIG_PORT_DATA_ROLE : 0) | + (pr == PR_SRC ? MSG_CONFIG_PORT_POWER_ROLE : 0))); +} + +int pd_phy_open(struct pd_phy_params *params) +{ + int ret; + struct usb_pdphy *pdphy = __pdphy; + + if (!pdphy) { + dev_err(pdphy->dev, "%s: pdphy not found\n", __func__); + return -ENODEV; + } + + if (pdphy->is_opened) { + dev_err(pdphy->dev, "%s: already opened\n", __func__); + return -EBUSY; + } + + pdphy->signal_cb = params->signal_cb; + pdphy->msg_rx_cb = params->msg_rx_cb; + pdphy->shutdown_cb = params->shutdown_cb; + pdphy->data_role = params->data_role; + pdphy->power_role = params->power_role; + pdphy->frame_filter_val = params->frame_filter_val; + + dev_dbg(pdphy->dev, "%s: DR %x PR %x frame filter val %x\n", __func__, + pdphy->data_role, pdphy->power_role, pdphy->frame_filter_val); + + ret = pdphy_enable_power(pdphy, true); + if (ret) + return ret; + + /* update data and power role to be used in GoodCRC generation */ + ret = pd_phy_update_roles(pdphy->data_role, pdphy->power_role); + if (ret) + return ret; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_EN_CONTROL, 0); + if (ret) + return ret; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_EN_CONTROL, CONTROL_ENABLE); + if (ret) + return ret; + + /* update frame filter */ + ret = pdphy_reg_write(pdphy, USB_PDPHY_FRAME_FILTER, + pdphy->frame_filter_val); + if (ret) + return ret; + + /* initialize Rx buffer ownership to PDPHY HW */ + ret = pdphy_reg_write(pdphy, USB_PDPHY_RX_ACKNOWLEDGE, 0); + if (ret) + return ret; + + pdphy->is_opened = true; + pdphy_enable_irq(pdphy, true); + + return ret; +} +EXPORT_SYMBOL(pd_phy_open); + +int pd_phy_signal(enum pd_sig_type type, unsigned int timeout_ms) +{ + u8 val; + int ret; + struct usb_pdphy *pdphy = __pdphy; + + dev_dbg(pdphy->dev, "%s: type %d timeout %u\n", __func__, type, + timeout_ms); + + if (!pdphy) { + dev_err(pdphy->dev, "%s: pdphy not found\n", __func__); + return -ENODEV; + } + + if (!pdphy->is_opened) { + dev_dbg(pdphy->dev, "%s: pdphy disabled\n", __func__); + return -ENODEV; + } + + pdphy->tx_status = -EINPROGRESS; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0); + if (ret) + return ret; + + usleep_range(2, 3); + + val = (type == CABLE_RESET_SIG ? TX_CONTROL_FRAME_TYPE_CABLE_RESET : 0) + | TX_CONTROL_SEND_SIGNAL; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, val); + if (ret) + return ret; + + ret = wait_event_interruptible_timeout(pdphy->tx_waitq, + pdphy->tx_status != -EINPROGRESS, msecs_to_jiffies(timeout_ms)); + if (ret <= 0) { + dev_err(pdphy->dev, "%s: failed ret %d", __func__, ret); + return ret ? ret : -ETIMEDOUT; + } + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0); + + if (pdphy->tx_status) + return pdphy->tx_status; + + if (type == HARD_RESET_SIG) + /* Frame filter is reconfigured in pd_phy_open() */ + return pdphy_reg_write(pdphy, USB_PDPHY_FRAME_FILTER, 0); + + return 0; +} +EXPORT_SYMBOL(pd_phy_signal); + +int pd_phy_write(u16 hdr, const u8 *data, size_t data_len, + enum pd_msg_type type, unsigned int timeout_ms) +{ + u8 val; + int ret; + size_t total_len = data_len + USB_PDPHY_MSG_HDR_LEN; + struct usb_pdphy *pdphy = __pdphy; + + dev_dbg(pdphy->dev, "%s: hdr %x frame type %d timeout %u\n", + __func__, hdr, type, timeout_ms); + print_hex_dump_debug("tx data obj:", DUMP_PREFIX_NONE, 32, 4, + data, data_len, false); + + if (!pdphy) { + dev_err(pdphy->dev, "%s: pdphy not found\n", __func__); + return -ENODEV; + } + + if (!pdphy->is_opened) { + dev_dbg(pdphy->dev, "%s: pdphy disabled\n", __func__); + return -ENODEV; + } + + if (data_len > USB_PDPHY_MAX_DATA_OBJ_LEN) { + dev_err(pdphy->dev, "%s: invalid data object len %zu\n", + __func__, data_len); + return -EINVAL; + } + + pdphy->tx_status = -EINPROGRESS; + + /* write 2 byte SOP message header */ + ret = pdphy_bulk_reg_write(pdphy, USB_PDPHY_TX_BUFFER_HDR, (u8 *)&hdr, + USB_PDPHY_MSG_HDR_LEN); + if (ret) + return ret; + + if (data_len) { + /* write data objects of SOP message */ + ret = pdphy_bulk_reg_write(pdphy, USB_PDPHY_TX_BUFFER_DATA, + data, data_len); + if (ret) + return ret; + } + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_SIZE, total_len - 1); + if (ret) + return ret; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0); + if (ret) + return ret; + + usleep_range(2, 3); + + val = TX_CONTROL_RETRY_COUNT | (type << 2) | TX_CONTROL_SEND_MSG; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, val); + if (ret) + return ret; + + ret = wait_event_interruptible_timeout(pdphy->tx_waitq, + pdphy->tx_status != -EINPROGRESS, msecs_to_jiffies(timeout_ms)); + if (ret <= 0) { + dev_err(pdphy->dev, "%s: failed ret %d", __func__, ret); + return ret ? ret : -ETIMEDOUT; + } + + if (hdr && !pdphy->tx_status) + pdphy->tx_bytes += data_len + USB_PDPHY_MSG_HDR_LEN; + + return pdphy->tx_status ? pdphy->tx_status : data_len; +} +EXPORT_SYMBOL(pd_phy_write); + +void pd_phy_close(void) +{ + int ret; + struct usb_pdphy *pdphy = __pdphy; + + if (!pdphy) { + dev_err(pdphy->dev, "%s: pdphy not found\n", __func__); + return; + } + + if (!pdphy->is_opened) { + dev_err(pdphy->dev, "%s: not opened\n", __func__); + return; + } + + pdphy->is_opened = false; + pdphy_enable_irq(pdphy, false); + + pdphy->tx_status = -ESHUTDOWN; + + wake_up_all(&pdphy->tx_waitq); + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, 0); + if (ret) + return; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_EN_CONTROL, 0); + if (ret) + return; + + pdphy_enable_power(pdphy, false); +} +EXPORT_SYMBOL(pd_phy_close); + +static irqreturn_t pdphy_msg_tx_irq(int irq, void *data) +{ + struct usb_pdphy *pdphy = data; + + if (irq == pdphy->msg_tx_irq) { + pdphy->msg_tx_cnt++; + pdphy->tx_status = 0; + } else if (irq == pdphy->msg_tx_discarded_irq) { + pdphy->msg_tx_discarded_cnt++; + pdphy->tx_status = -EBUSY; + } else if (irq == pdphy->msg_tx_failed_irq) { + pdphy->msg_tx_failed_cnt++; + pdphy->tx_status = -EFAULT; + } else { + dev_err(pdphy->dev, "spurious irq #%d received\n", irq); + return IRQ_NONE; + } + + wake_up(&pdphy->tx_waitq); + + return IRQ_HANDLED; +} + +static irqreturn_t pdphy_msg_rx_discarded_irq(int irq, void *data) +{ + struct usb_pdphy *pdphy = data; + + pdphy->msg_rx_discarded_cnt++; + + return IRQ_HANDLED; +} + +static irqreturn_t pdphy_sig_rx_irq_thread(int irq, void *data) +{ + u8 rx_status, frame_type; + int ret; + struct usb_pdphy *pdphy = data; + + pdphy->sig_rx_cnt++; + + ret = pdphy_reg_read(pdphy, &rx_status, USB_PDPHY_RX_STATUS, 1); + if (ret) + goto done; + + frame_type = rx_status & RX_FRAME_TYPE; + if (frame_type != HARD_RESET_SIG) { + dev_err(pdphy->dev, "%s:unsupported frame type %d\n", + __func__, frame_type); + goto done; + } + + /* Frame filter is reconfigured in pd_phy_open() */ + ret = pdphy_reg_write(pdphy, USB_PDPHY_FRAME_FILTER, 0); + + if (pdphy->signal_cb) + pdphy->signal_cb(pdphy->usbpd, frame_type); + +done: + return IRQ_HANDLED; +} + +static irqreturn_t pdphy_sig_tx_irq_thread(int irq, void *data) +{ + struct usb_pdphy *pdphy = data; + + pdphy->sig_tx_cnt++; + pdphy->tx_status = 0; + wake_up(&pdphy->tx_waitq); + + return IRQ_HANDLED; +} + +static irqreturn_t pdphy_msg_rx_irq_thread(int irq, void *data) +{ + u8 size, rx_status, frame_type; + u8 *buf = NULL; + int ret; + struct usb_pdphy *pdphy = data; + + pdphy->msg_rx_cnt++; + + ret = pdphy_reg_read(pdphy, &size, USB_PDPHY_RX_SIZE, 1); + if (ret) + goto done; + + if (!size) { + dev_err(pdphy->dev, "%s: incorrect size 1byte\n", + __func__); + goto done; + } + + ret = pdphy_reg_read(pdphy, &rx_status, USB_PDPHY_RX_STATUS, 1); + if (ret) + goto done; + + frame_type = rx_status & RX_FRAME_TYPE; + if (frame_type != SOP_MSG) { + dev_err(pdphy->dev, "%s:unsupported frame type %d\n", + __func__, frame_type); + goto done; + } + + buf = kmalloc(size + 1, GFP_KERNEL); + if (!buf) + goto done; + + ret = pdphy_reg_read(pdphy, buf, USB_PDPHY_RX_BUFFER, size + 1); + if (ret) + goto done; + + /* ack to change ownership of rx buffer back to PDPHY RX HW */ + pdphy_reg_write(pdphy, USB_PDPHY_RX_ACKNOWLEDGE, 0); + + if (pdphy->msg_rx_cb) + pdphy->msg_rx_cb(pdphy->usbpd, frame_type, buf, size + 1); + + print_hex_dump_debug("rx msg:", DUMP_PREFIX_NONE, 32, 4, buf, size + 1, + false); + pdphy->rx_bytes += size + 1; +done: + kfree(buf); + return IRQ_HANDLED; +} + +static int pdphy_request_irq(struct usb_pdphy *pdphy, + struct device_node *node, + int *irq_num, const char *irq_name, + irqreturn_t (irq_handler)(int irq, void *data), + irqreturn_t (thread_fn)(int irq, void *data), + int flags) +{ + int ret; + + *irq_num = of_irq_get_byname(node, irq_name); + if (*irq_num < 0) { + dev_err(pdphy->dev, "Unable to get %s irqn", irq_name); + ret = -ENXIO; + } + + irq_set_status_flags(*irq_num, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(pdphy->dev, *irq_num, irq_handler, + thread_fn, flags, irq_name, pdphy); + if (ret < 0) { + dev_err(pdphy->dev, "Unable to request %s irq: %dn", + irq_name, ret); + ret = -ENXIO; + } + + return 0; +} + +static int pdphy_probe(struct platform_device *pdev) +{ + int ret; + unsigned int base; + struct usb_pdphy *pdphy; + + pdphy = devm_kzalloc(&pdev->dev, sizeof(*pdphy), GFP_KERNEL); + if (!pdphy) + return -ENOMEM; + + pdphy->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!pdphy->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + dev_set_drvdata(&pdev->dev, pdphy); + + ret = of_property_read_u32(pdev->dev.of_node, "reg", &base); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get reg base address ret = %d\n", + ret); + return ret; + } + + pdphy->base = base; + pdphy->dev = &pdev->dev; + + init_waitqueue_head(&pdphy->tx_waitq); + + pdphy->vdd_pdphy = devm_regulator_get(&pdev->dev, "vdd-pdphy"); + if (IS_ERR(pdphy->vdd_pdphy)) { + dev_err(&pdev->dev, "unable to get vdd-pdphy\n"); + return PTR_ERR(pdphy->vdd_pdphy); + } + + ret = pdphy_request_irq(pdphy, pdev->dev.of_node, + &pdphy->sig_tx_irq, "sig-tx", NULL, + pdphy_sig_tx_irq_thread, (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (ret < 0) + return ret; + + ret = pdphy_request_irq(pdphy, pdev->dev.of_node, + &pdphy->sig_rx_irq, "sig-rx", NULL, + pdphy_sig_rx_irq_thread, (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (ret < 0) + return ret; + + ret = pdphy_request_irq(pdphy, pdev->dev.of_node, + &pdphy->msg_tx_irq, "msg-tx", pdphy_msg_tx_irq, + NULL, (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (ret < 0) + return ret; + + ret = pdphy_request_irq(pdphy, pdev->dev.of_node, + &pdphy->msg_rx_irq, "msg-rx", NULL, + pdphy_msg_rx_irq_thread, (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (ret < 0) + return ret; + + ret = pdphy_request_irq(pdphy, pdev->dev.of_node, + &pdphy->msg_tx_failed_irq, "msg-tx-failed", pdphy_msg_tx_irq, + NULL, (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (ret < 0) + return ret; + + ret = pdphy_request_irq(pdphy, pdev->dev.of_node, + &pdphy->msg_tx_discarded_irq, "msg-tx-discarded", + pdphy_msg_tx_irq, NULL, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (ret < 0) + return ret; + + ret = pdphy_request_irq(pdphy, pdev->dev.of_node, + &pdphy->msg_rx_discarded_irq, "msg-rx-discarded", + pdphy_msg_rx_discarded_irq, NULL, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (ret < 0) + return ret; + + pdphy->usbpd = usbpd_create(&pdev->dev); + if (IS_ERR(pdphy->usbpd)) { + dev_err(&pdev->dev, "usbpd_create failed: %ld\n", + PTR_ERR(pdphy->usbpd)); + return PTR_ERR(pdphy->usbpd); + } + + __pdphy = pdphy; + + pdphy_create_debugfs_entries(pdphy); + + return 0; +} + +static int pdphy_remove(struct platform_device *pdev) +{ + struct usb_pdphy *pdphy = platform_get_drvdata(pdev); + + debugfs_remove_recursive(pdphy->debug_root); + usbpd_destroy(pdphy->usbpd); + + if (pdphy->is_opened) + pd_phy_close(); + + __pdphy = NULL; + + return 0; +} + +static void pdphy_shutdown(struct platform_device *pdev) +{ + struct usb_pdphy *pdphy = platform_get_drvdata(pdev); + + /* let protocol engine shutdown the pdphy synchronously */ + if (pdphy->shutdown_cb) + pdphy->shutdown_cb(pdphy->usbpd); +} + +static const struct of_device_id pdphy_match_table[] = { + { + .compatible = "qcom,qpnp-pdphy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, pdphy_match_table); + +static struct platform_driver pdphy_driver = { + .driver = { + .name = "qpnp-pdphy", + .of_match_table = pdphy_match_table, + }, + .probe = pdphy_probe, + .remove = pdphy_remove, + .shutdown = pdphy_shutdown, +}; + +module_platform_driver(pdphy_driver); + +MODULE_DESCRIPTION("QPNP PD PHY Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qpnp-pdphy"); diff --git a/drivers/usb/pd/usbpd.h b/drivers/usb/pd/usbpd.h index 7a24d33190e4..fd3f2f33bbc7 100644 --- a/drivers/usb/pd/usbpd.h +++ b/drivers/usb/pd/usbpd.h @@ -40,4 +40,62 @@ enum power_role { PR_SRC = 1, }; +enum pd_sig_type { + HARD_RESET_SIG = 0, + CABLE_RESET_SIG, +}; + +enum pd_msg_type { + SOP_MSG = 0, + SOPI_MSG, + SOPII_MSG, +}; + +/* enable msg and signal to be received by phy */ +#define FRAME_FILTER_EN_SOP BIT(0) +#define FRAME_FILTER_EN_HARD_RESET BIT(5) + +struct pd_phy_params { + void (*signal_cb)(struct usbpd *pd, enum pd_sig_type type); + void (*msg_rx_cb)(struct usbpd *pd, enum pd_msg_type type, + u8 *buf, size_t len); + void (*shutdown_cb)(struct usbpd *pd); + enum data_role data_role; + enum power_role power_role; + u8 frame_filter_val; +}; + +#if IS_ENABLED(CONFIG_QPNP_USB_PDPHY) +int pd_phy_open(struct pd_phy_params *params); +int pd_phy_signal(enum pd_sig_type type, unsigned int timeout_ms); +int pd_phy_write(u16 hdr, const u8 *data, size_t data_len, + enum pd_msg_type type, unsigned int timeout_ms); +int pd_phy_update_roles(enum data_role dr, enum power_role pr); +void pd_phy_close(void); +#else +static inline int pd_phy_open(struct pd_phy_params *params) +{ + return -ENODEV; +} + +static inline int pd_phy_signal(enum pd_sig_type type, unsigned int timeout_ms) +{ + return -ENODEV; +} + +static inline int pd_phy_write(u16 hdr, const u8 *data, size_t data_len, + enum pd_msg_type type, unsigned int timeout_ms) +{ + return -ENODEV; +} + +static inline int pd_phy_update_roles(enum data_role dr, enum power_role pr) +{ + return -ENODEV; +} + +static inline void pd_phy_close(void) +{ +} +#endif #endif /* _USBPD_H */