usb: pd: Add QPNP Power Delivery PHY driver

The QPNP PD PHY resides in the PMIC and handles USB Power Delivery
data transmission and reception over the CC lines. This driver
communicates to this device over SPMI or I2C buses. Introduce APIs
that upper layers will use to implement the protocol layer and
policy engine.

Change-Id: I75dec23c297fd5e07d14741e6627b473012b7a01
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
Signed-off-by: Jack Pham <jackp@codeaurora.org>
This commit is contained in:
Hemant Kumar 2016-02-29 12:01:27 -08:00 committed by Jeevan Shriram
parent 82a0ca0e2d
commit e6b3a48a1e
5 changed files with 960 additions and 0 deletions

View file

@ -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";
};

View file

@ -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

View file

@ -3,3 +3,4 @@
#
obj-$(CONFIG_USB_PD_POLICY) += policy_engine.o
obj-$(CONFIG_QPNP_USB_PDPHY) += qpnp-pdphy.o

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

@ -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 <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 "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");

View file

@ -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 */