usb: pd: Add support of qpnp-pdphy and policy_engine drivers

The qpnp-pdphy driver provides support for the PD PHY peripheral.
This along with the policy engine provides a protocol stack that
supports USB Power Delivery communication over a Type-C port.
This snapshot is taken as of msm-4.19 commit <1526c9c655ff>
("Merge "mmc: sdhci-msm: Add quirk to control debug feature"").

Fixed the compilation issues related to typeC framework for
the support of role swap. Also, remove unsupported and unused
extcon framework calls and API.

Change-Id: Ie89bd60b773a3c61f26b92cb1e1ef09847b6d46e
Signed-off-by: Sriharsha Allenki <sallenki@codeaurora.org>
This commit is contained in:
Sriharsha Allenki 2020-06-09 13:37:24 +05:30
parent cddf9787a3
commit d9af4afe63
8 changed files with 6044 additions and 0 deletions

View File

@ -174,4 +174,6 @@ source "drivers/usb/typec/Kconfig"
source "drivers/usb/roles/Kconfig"
source "drivers/usb/pd/Kconfig"
endif # USB_SUPPORT

View File

@ -61,6 +61,7 @@ obj-$(CONFIG_USB_CHIPIDEA) += chipidea/
obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs/
obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_PD) += pd/
obj-$(CONFIG_USBIP_CORE) += usbip/
obj-$(CONFIG_TYPEC) += typec/

31
drivers/usb/pd/Kconfig Normal file
View File

@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# USB Power Delivery driver configuration
#
menu "USB Power Delivery"
config USB_PD
def_bool n
config USB_PD_POLICY
tristate "USB Power Delivery Protocol and Policy Engine"
depends on EXTCON
depends on TYPEC
select USB_PD
help
Say Y here to enable USB PD protocol and policy engine.
This driver provides a class that implements the upper
layers of the USB Power Delivery stack. It requires a
PD PHY driver in order to transmit and receive PD
messages on its behalf.
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

7
drivers/usb/pd/Makefile Normal file
View File

@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for USB Power Delivery drivers
#
obj-$(CONFIG_USB_PD_POLICY) += policy_engine.o
obj-$(CONFIG_QPNP_USB_PDPHY) += qpnp-pdphy.o

File diff suppressed because it is too large Load Diff

929
drivers/usb/pd/qpnp-pdphy.c Normal file
View File

@ -0,0 +1,929 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#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(n) (((n) & 0x3) << 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
/* VDD regulator */
#define VDD_PDPHY_VOL_MIN 2800000 /* uV */
#define VDD_PDPHY_VOL_MAX 3300000 /* uV */
#define VDD_PDPHY_HPM_LOAD 3000 /* uA */
/* Message Spec Rev field */
#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3)
/* 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;
bool sig_rx_wake_enabled;
bool msg_rx_wake_enabled;
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;
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", 0400, 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);
pdphy->sig_rx_wake_enabled =
!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);
pdphy->msg_rx_wake_enabled =
!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);
if (pdphy->sig_rx_wake_enabled) {
disable_irq_wake(pdphy->sig_rx_irq);
pdphy->sig_rx_wake_enabled = false;
}
disable_irq(pdphy->msg_tx_irq);
if (!pdphy->in_test_data_mode)
disable_irq(pdphy->msg_rx_irq);
if (pdphy->msg_rx_wake_enabled) {
disable_irq_wake(pdphy->msg_rx_irq);
pdphy->msg_rx_wake_enabled = false;
}
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_update_frame_filter(u8 frame_filter_val)
{
struct usb_pdphy *pdphy = __pdphy;
return pdphy_reg_write(pdphy, USB_PDPHY_FRAME_FILTER, frame_filter_val);
}
EXPORT_SYMBOL(pd_phy_update_frame_filter);
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_hrtimeout(pdphy->tx_waitq,
pdphy->tx_status != -EINPROGRESS,
ms_to_ktime(HARD_RESET_COMPLETE_TIME));
if (ret) {
dev_err(pdphy->dev, "%s: failed ret %d\n", __func__, ret);
return ret;
}
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;
unsigned int msg_rx_cnt;
if (!pdphy) {
pr_err("%s: pdphy not found\n", __func__);
return -ENODEV;
}
msg_rx_cnt = pdphy->msg_rx_cnt;
if (!pdphy->is_opened) {
dev_dbg(pdphy->dev, "%s: pdphy disabled\n", __func__);
return -ENODEV;
}
dev_dbg(pdphy->dev, "%s: hdr %x frame sop_type %d\n",
__func__, hdr, sop);
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) {
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) {
print_hex_dump_debug("tx data obj:", DUMP_PREFIX_NONE, 32, 4,
data, data_len, false);
/* 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 = (sop << 2) | TX_CONTROL_SEND_MSG;
/* nRetryCount == 2 for PD 3.0, 3 for PD 2.0 */
if (PD_MSG_HDR_REV(hdr) == USBPD_REV_30)
val |= TX_CONTROL_RETRY_COUNT(2);
else
val |= TX_CONTROL_RETRY_COUNT(3);
if (msg_rx_cnt != pdphy->msg_rx_cnt) {
dev_err(pdphy->dev, "%s: RX message arrived\n", __func__);
return -EBUSY;
}
ret = pdphy_reg_write(pdphy, USB_PDPHY_TX_CONTROL, val);
if (ret)
return ret;
ret = wait_event_interruptible_hrtimeout(pdphy->tx_waitq,
pdphy->tx_status != -EINPROGRESS,
ms_to_ktime(RECEIVER_RESPONSE_TIME));
if (ret) {
dev_err(pdphy->dev, "%s: failed ret %d\n", __func__, ret);
return ret;
}
if (!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;
/* TX already aborted by received signal */
if (pdphy->tx_status != -EINPROGRESS)
return IRQ_HANDLED;
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);
if (pdphy->tx_status == -EINPROGRESS) {
pdphy->tx_status = -EBUSY;
wake_up(&pdphy->tx_waitq);
}
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)
{
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 == SOPII_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) && !(buf[1] & 0x80) && size >= 5) {
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:
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 irq\n", 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: %d\n",
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,
NULL, (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;
/* 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");

106
drivers/usb/pd/usbpd.h Normal file
View File

@ -0,0 +1,106 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#ifndef _USBPD_H
#define _USBPD_H
#include <linux/device.h>
struct usbpd;
#if IS_ENABLED(CONFIG_USB_PD_POLICY)
struct usbpd *usbpd_create(struct device *parent);
void usbpd_destroy(struct usbpd *pd);
#else
static inline struct usbpd *usbpd_create(struct device *parent)
{
return ERR_PTR(-ENODEV);
}
static inline void usbpd_destroy(struct usbpd *pd) { }
#endif
enum data_role {
DR_NONE = -1,
DR_UFP = 0,
DR_DFP = 1,
};
enum power_role {
PR_NONE = -1,
PR_SINK = 0,
PR_SRC = 1,
};
enum pd_sig_type {
HARD_RESET_SIG = 0,
CABLE_RESET_SIG,
};
enum pd_sop_type {
SOP_MSG = 0,
SOPI_MSG,
SOPII_MSG,
};
enum pd_spec_rev {
USBPD_REV_20 = 1,
USBPD_REV_30 = 2,
};
/* enable msg and signal to be received by phy */
#define FRAME_FILTER_EN_SOP BIT(0)
#define FRAME_FILTER_EN_SOPI BIT(1)
#define FRAME_FILTER_EN_HARD_RESET BIT(5)
struct pd_phy_params {
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);
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 sig);
int pd_phy_write(u16 hdr, const u8 *data, size_t data_len,
enum pd_sop_type sop);
int pd_phy_update_roles(enum data_role dr, enum power_role pr);
int pd_phy_update_frame_filter(u8 frame_filter_val);
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)
{
return -ENODEV;
}
static inline int pd_phy_write(u16 hdr, const u8 *data, size_t data_len,
enum pd_sop_type sop)
{
return -ENODEV;
}
static inline int pd_phy_update_roles(enum data_role dr, enum power_role pr)
{
return -ENODEV;
}
static inline int pd_phy_update_frame_filter(u8 frame_filter_val)
{
return -ENODEV;
}
static inline void pd_phy_close(void)
{
}
#endif
#endif /* _USBPD_H */

168
include/linux/usb/usbpd.h Normal file
View File

@ -0,0 +1,168 @@
/* Copyright (c) 2016-2019, The 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.
*/
#ifndef __LINUX_USB_USBPD_H
#define __LINUX_USB_USBPD_H
#include <linux/list.h>
struct usbpd;
/* Standard IDs */
#define USBPD_SID 0xff00
/* Structured VDM Command Type */
enum usbpd_svdm_cmd_type {
SVDM_CMD_TYPE_INITIATOR,
SVDM_CMD_TYPE_RESP_ACK,
SVDM_CMD_TYPE_RESP_NAK,
SVDM_CMD_TYPE_RESP_BUSY,
};
/* Structured VDM Commands */
#define USBPD_SVDM_DISCOVER_IDENTITY 0x1
#define USBPD_SVDM_DISCOVER_SVIDS 0x2
#define USBPD_SVDM_DISCOVER_MODES 0x3
#define USBPD_SVDM_ENTER_MODE 0x4
#define USBPD_SVDM_EXIT_MODE 0x5
#define USBPD_SVDM_ATTENTION 0x6
/*
* Implemented by client
*/
struct usbpd_svid_handler {
u16 svid;
/* Notified when VDM session established/reset; must be implemented */
void (*connect)(struct usbpd_svid_handler *hdlr,
bool supports_usb_comm);
void (*disconnect)(struct usbpd_svid_handler *hdlr);
/* DP driver -> PE driver for requesting USB SS lanes */
int (*request_usb_ss_lane)(struct usbpd *pd,
struct usbpd_svid_handler *hdlr);
/* Unstructured VDM */
void (*vdm_received)(struct usbpd_svid_handler *hdlr, u32 vdm_hdr,
const u32 *vdos, int num_vdos);
/* Structured VDM */
void (*svdm_received)(struct usbpd_svid_handler *hdlr, u8 cmd,
enum usbpd_svdm_cmd_type cmd_type, const u32 *vdos,
int num_vdos);
/* client should leave these blank; private members used by PD driver */
struct list_head entry;
bool discovered;
};
enum plug_orientation {
ORIENTATION_NONE,
ORIENTATION_CC1,
ORIENTATION_CC2,
};
#if IS_ENABLED(CONFIG_USB_PD_POLICY)
/*
* Obtains an instance of usbpd from a DT phandle
*/
struct usbpd *devm_usbpd_get_by_phandle(struct device *dev,
const char *phandle);
/*
* Called by client to handle specific SVID messages.
* Specify callback functions in the usbpd_svid_handler argument
*/
int usbpd_register_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr);
void usbpd_unregister_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr);
/*
* Transmit a VDM message.
*/
int usbpd_send_vdm(struct usbpd *pd, u32 vdm_hdr, const u32 *vdos,
int num_vdos);
/*
* Transmit a Structured VDM message.
*/
int usbpd_send_svdm(struct usbpd *pd, u16 svid, u8 cmd,
enum usbpd_svdm_cmd_type cmd_type, int obj_pos,
const u32 *vdos, int num_vdos);
/*
* Get current status of CC pin orientation.
*
* Return: ORIENTATION_CC1 or ORIENTATION_CC2 if attached,
* otherwise ORIENTATION_NONE if not attached
*/
enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd);
void usbpd_vdm_in_suspend(struct usbpd *pd, bool in_suspend);
#else
static inline struct usbpd *devm_usbpd_get_by_phandle(struct device *dev,
const char *phandle)
{
return ERR_PTR(-ENODEV);
}
static inline int usbpd_register_svid(struct usbpd *pd,
struct usbpd_svid_handler *hdlr)
{
return -EINVAL;
}
static inline void usbpd_unregister_svid(struct usbpd *pd,
struct usbpd_svid_handler *hdlr)
{
}
static inline int usbpd_send_vdm(struct usbpd *pd, u32 vdm_hdr, const u32 *vdos,
int num_vdos)
{
return -EINVAL;
}
static inline int usbpd_send_svdm(struct usbpd *pd, u16 svid, u8 cmd,
enum usbpd_svdm_cmd_type cmd_type, int obj_pos,
const u32 *vdos, int num_vdos)
{
return -EINVAL;
}
static inline enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd)
{
return ORIENTATION_NONE;
}
static inline void usbpd_vdm_in_suspend(struct usbpd *pd, bool in_suspend) { }
#endif /* IS_ENABLED(CONFIG_USB_PD_POLICY) */
/*
* Additional helpers for Enter/Exit Mode commands
*/
static inline int usbpd_enter_mode(struct usbpd *pd, u16 svid, int mode,
const u32 *vdo)
{
return usbpd_send_svdm(pd, svid, USBPD_SVDM_ENTER_MODE,
SVDM_CMD_TYPE_INITIATOR, mode, vdo, vdo ? 1 : 0);
}
static inline int usbpd_exit_mode(struct usbpd *pd, u16 svid, int mode,
const u32 *vdo)
{
return usbpd_send_svdm(pd, svid, USBPD_SVDM_EXIT_MODE,
SVDM_CMD_TYPE_INITIATOR, mode, vdo, vdo ? 1 : 0);
}
#endif /* __LINUX_USB_USBPD_H */