diff options
Diffstat (limited to 'drivers/usb/pd/qpnp-pdphy.c')
-rw-r--r-- | drivers/usb/pd/qpnp-pdphy.c | 917 |
1 files changed, 917 insertions, 0 deletions
diff --git a/drivers/usb/pd/qpnp-pdphy.c b/drivers/usb/pd/qpnp-pdphy.c new file mode 100644 index 000000000000..b454442e2471 --- /dev/null +++ b/drivers/usb/pd/qpnp-pdphy.c @@ -0,0 +1,917 @@ +/* Copyright (c) 2016-2017, 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/irq.h> +#include <linux/of_irq.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/sched.h> +#include <linux/wait.h> +#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 MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0)) + +#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_BIST_MODE 0x4E +#define BIST_MODE_MASK 0xF +#define BIST_ENABLE BIT(7) +#define PD_MSG_BIST 0x3 +#define PD_BIST_TEST_DATA_MODE 0x8 + +#define USB_PDPHY_TX_BUFFER_HDR 0x60 +#define USB_PDPHY_TX_BUFFER_DATA 0x62 + +#define USB_PDPHY_RX_BUFFER 0x80 + +#define USB_PDPHY_SEC_ACCESS 0xD0 +#define USB_PDPHY_TRIM_3 0xF3 + +/* VDD regulator */ +#define VDD_PDPHY_VOL_MIN 2800000 /* uV */ +#define VDD_PDPHY_VOL_MAX 3300000 /* uV */ +#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ + +/* timers */ +#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */ +#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */ + +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 sig); + void (*msg_rx_cb)(struct usbpd *pd, enum pd_sop_type sop, + 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; + bool in_test_data_mode; + bool rx_busy; + + 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_wake(pdphy->sig_rx_irq); + enable_irq(pdphy->msg_tx_irq); + if (!pdphy->in_test_data_mode) { + enable_irq(pdphy->msg_rx_irq); + enable_irq_wake(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_wake(pdphy->sig_rx_irq); + disable_irq(pdphy->msg_tx_irq); + if (!pdphy->in_test_data_mode) { + disable_irq(pdphy->msg_rx_irq); + disable_irq_wake(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))); +} +EXPORT_SYMBOL(pd_phy_update_roles); + +int pd_phy_open(struct pd_phy_params *params) +{ + int ret; + struct usb_pdphy *pdphy = __pdphy; + + if (!pdphy) { + pr_err("%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; + + /* PD 2.0 phy */ + ret = pdphy_masked_write(pdphy, USB_PDPHY_MSG_CONFIG, + MSG_CONFIG_SPEC_REV_MASK, USBPD_REV_20); + 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 sig) +{ + u8 val; + int ret; + struct usb_pdphy *pdphy = __pdphy; + + dev_dbg(pdphy->dev, "%s: type %d\n", __func__, sig); + + if (!pdphy) { + pr_err("%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 = (sig == 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(HARD_RESET_COMPLETE_TIME)); + 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 (sig == 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_sop_type sop) +{ + 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 sop_type %d\n", + __func__, hdr, sop); + + if (data && data_len) + print_hex_dump_debug("tx data obj:", DUMP_PREFIX_NONE, 32, 4, + data, data_len, false); + + if (!pdphy) { + pr_err("%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; + } + + ret = pdphy_reg_read(pdphy, &val, USB_PDPHY_RX_ACKNOWLEDGE, 1); + if (ret || val || pdphy->rx_busy) { + dev_err(pdphy->dev, "%s: RX message pending\n", __func__); + return -EBUSY; + } + + 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 && 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 | (sop << 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(RECEIVER_RESPONSE_TIME)); + 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 : 0; +} +EXPORT_SYMBOL(pd_phy_write); + +void pd_phy_close(void) +{ + int ret; + struct usb_pdphy *pdphy = __pdphy; + + if (!pdphy) { + pr_err("%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); + + pdphy_reg_write(pdphy, USB_PDPHY_BIST_MODE, 0); + pdphy->in_test_data_mode = false; + + 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; + + /* in case of exit from BIST Carrier Mode 2, clear BIST_MODE */ + pdphy_reg_write(pdphy, USB_PDPHY_BIST_MODE, 0); + + pdphy->sig_tx_cnt++; + pdphy->tx_status = 0; + wake_up(&pdphy->tx_waitq); + + return IRQ_HANDLED; +} + +static int pd_phy_bist_mode(u8 bist_mode) +{ + struct usb_pdphy *pdphy = __pdphy; + + dev_dbg(pdphy->dev, "%s: enter BIST mode %d\n", __func__, bist_mode); + + pdphy_reg_write(pdphy, USB_PDPHY_BIST_MODE, 0); + + udelay(5); + + return pdphy_masked_write(pdphy, USB_PDPHY_BIST_MODE, + BIST_MODE_MASK | BIST_ENABLE, bist_mode | BIST_ENABLE); +} + +static irqreturn_t pdphy_msg_rx_irq(int irq, void *data) +{ + struct usb_pdphy *pdphy = data; + + pdphy->rx_busy = true; + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t pdphy_msg_rx_irq_thread(int irq, void *data) +{ + u8 size, rx_status, frame_type; + u8 buf[32]; + 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 || size > 31) { + dev_err(pdphy->dev, "%s: invalid size %d\n", __func__, size); + 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; + } + + 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 (((buf[0] & 0xf) == PD_MSG_BIST) && size >= 5) { /* BIST */ + u8 mode = buf[5] >> 4; /* [31:28] of 1st data object */ + + pd_phy_bist_mode(mode); + pdphy_reg_write(pdphy, USB_PDPHY_RX_ACKNOWLEDGE, 0); + + if (mode == PD_BIST_TEST_DATA_MODE) { + pdphy->in_test_data_mode = true; + disable_irq_nosync(irq); + } + goto done; + } + + 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: + pdphy->rx_busy = false; + 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", pdphy_msg_rx_irq, + 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; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_SEC_ACCESS, 0xA5); + if (ret) + return ret; + + ret = pdphy_reg_write(pdphy, USB_PDPHY_TRIM_3, 0x2); + if (ret) + return ret; + + /* usbpd_create() could call back to us, so have __pdphy ready */ + __pdphy = pdphy; + + pdphy->usbpd = usbpd_create(&pdev->dev); + if (IS_ERR(pdphy->usbpd)) { + dev_err(&pdev->dev, "usbpd_create failed: %ld\n", + PTR_ERR(pdphy->usbpd)); + __pdphy = NULL; + return PTR_ERR(pdphy->usbpd); + } + + 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"); |