mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
usb: typec: intel_pmc_mux: Check the port status before connect
The PMC microcontroller that we use for configuration, does not supply any status information back. For port status we need to talk to another controller on the board called IOM (I/O manager). By checking the port status before configuring the muxes, we can make sure that we do not reconfigure the port after bootup when the system firmware (for example BIOS) has already configured it. Using the status information also to check if DisplayPort HPD is still asserted when the cable plug is disconnected, and clearing it if it is. Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: Rajmohan Mani <rajmohan.mani@intel.com> Reviewed-by: Utkarsh Patel <utkarsh.h.patel@intel.com> Tested-by: Utkarsh Patel <utkarsh.h.patel@intel.com> Link: https://lore.kernel.org/r/20200907142428.35838-2-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
5df7ef7d32
commit
43d596e322
1 changed files with 141 additions and 11 deletions
|
@ -83,10 +83,48 @@ enum {
|
||||||
#define PMC_USB_DP_HPD_LVL BIT(4)
|
#define PMC_USB_DP_HPD_LVL BIT(4)
|
||||||
#define PMC_USB_DP_HPD_IRQ BIT(5)
|
#define PMC_USB_DP_HPD_IRQ BIT(5)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Input Output Manager (IOM) PORT STATUS
|
||||||
|
*/
|
||||||
|
#define IOM_PORT_STATUS_OFFSET 0x560
|
||||||
|
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_MASK GENMASK(9, 6)
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT 6
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_USB 0x03
|
||||||
|
/* activity type: Safe Mode */
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_SAFE_MODE 0x04
|
||||||
|
/* activity type: Display Port */
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP 0x05
|
||||||
|
/* activity type: Display Port Multi Function Device */
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP_MFD 0x06
|
||||||
|
/* activity type: Thunderbolt */
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_TBT 0x07
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_USB 0x0c
|
||||||
|
#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_TBT_USB 0x0d
|
||||||
|
/* Upstream Facing Port Information */
|
||||||
|
#define IOM_PORT_STATUS_UFP BIT(10)
|
||||||
|
/* Display Port Hot Plug Detect status */
|
||||||
|
#define IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK GENMASK(13, 12)
|
||||||
|
#define IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT 12
|
||||||
|
#define IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT 0x01
|
||||||
|
#define IOM_PORT_STATUS_DHPD_HPD_SOURCE_TBT BIT(14)
|
||||||
|
#define IOM_PORT_STATUS_CONNECTED BIT(31)
|
||||||
|
|
||||||
|
#define IOM_PORT_ACTIVITY_IS(_status_, _type_) \
|
||||||
|
((((_status_) & IOM_PORT_STATUS_ACTIVITY_TYPE_MASK) >> \
|
||||||
|
IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT) == \
|
||||||
|
(IOM_PORT_STATUS_ACTIVITY_TYPE_##_type_))
|
||||||
|
|
||||||
|
#define IOM_PORT_HPD_ASSERTED(_status_) \
|
||||||
|
((((_status_) & IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK) >> \
|
||||||
|
IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT) & \
|
||||||
|
IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT)
|
||||||
|
|
||||||
struct pmc_usb;
|
struct pmc_usb;
|
||||||
|
|
||||||
struct pmc_usb_port {
|
struct pmc_usb_port {
|
||||||
int num;
|
int num;
|
||||||
|
u32 iom_status;
|
||||||
struct pmc_usb *pmc;
|
struct pmc_usb *pmc;
|
||||||
struct typec_mux *typec_mux;
|
struct typec_mux *typec_mux;
|
||||||
struct typec_switch *typec_sw;
|
struct typec_switch *typec_sw;
|
||||||
|
@ -107,8 +145,16 @@ struct pmc_usb {
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
struct intel_scu_ipc_dev *ipc;
|
struct intel_scu_ipc_dev *ipc;
|
||||||
struct pmc_usb_port *port;
|
struct pmc_usb_port *port;
|
||||||
|
struct acpi_device *iom_adev;
|
||||||
|
void __iomem *iom_base;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void update_port_status(struct pmc_usb_port *port)
|
||||||
|
{
|
||||||
|
port->iom_status = readl(port->pmc->iom_base + IOM_PORT_STATUS_OFFSET +
|
||||||
|
port->usb3_port * sizeof(u32));
|
||||||
|
}
|
||||||
|
|
||||||
static int sbu_orientation(struct pmc_usb_port *port)
|
static int sbu_orientation(struct pmc_usb_port *port)
|
||||||
{
|
{
|
||||||
if (port->sbu_orientation)
|
if (port->sbu_orientation)
|
||||||
|
@ -145,18 +191,17 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_mux_state *state)
|
pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_displayport_data *dp)
|
||||||
{
|
{
|
||||||
struct typec_displayport_data *data = state->data;
|
|
||||||
u8 msg[2] = { };
|
u8 msg[2] = { };
|
||||||
|
|
||||||
msg[0] = PMC_USB_DP_HPD;
|
msg[0] = PMC_USB_DP_HPD;
|
||||||
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
||||||
|
|
||||||
if (data->status & DP_STATUS_IRQ_HPD)
|
if (dp->status & DP_STATUS_IRQ_HPD)
|
||||||
msg[1] = PMC_USB_DP_HPD_IRQ;
|
msg[1] = PMC_USB_DP_HPD_IRQ;
|
||||||
|
|
||||||
if (data->status & DP_STATUS_HPD_STATE)
|
if (dp->status & DP_STATUS_HPD_STATE)
|
||||||
msg[1] |= PMC_USB_DP_HPD_LVL;
|
msg[1] |= PMC_USB_DP_HPD_LVL;
|
||||||
|
|
||||||
return pmc_usb_command(port, msg, sizeof(msg));
|
return pmc_usb_command(port, msg, sizeof(msg));
|
||||||
|
@ -169,8 +214,18 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state)
|
||||||
struct altmode_req req = { };
|
struct altmode_req req = { };
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (IOM_PORT_ACTIVITY_IS(port->iom_status, DP) ||
|
||||||
|
IOM_PORT_ACTIVITY_IS(port->iom_status, DP_MFD)) {
|
||||||
|
if (IOM_PORT_HPD_ASSERTED(port->iom_status) &&
|
||||||
|
(!(data->status & DP_STATUS_IRQ_HPD) &&
|
||||||
|
data->status & DP_STATUS_HPD_STATE))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return pmc_usb_mux_dp_hpd(port, state->data);
|
||||||
|
}
|
||||||
|
|
||||||
if (data->status & DP_STATUS_IRQ_HPD)
|
if (data->status & DP_STATUS_IRQ_HPD)
|
||||||
return pmc_usb_mux_dp_hpd(port, state);
|
return pmc_usb_mux_dp_hpd(port, state->data);
|
||||||
|
|
||||||
req.usage = PMC_USB_ALT_MODE;
|
req.usage = PMC_USB_ALT_MODE;
|
||||||
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
||||||
|
@ -192,8 +247,8 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state)
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (data->status & DP_STATUS_HPD_STATE)
|
if (data->status & (DP_STATUS_IRQ_HPD | DP_STATUS_HPD_STATE))
|
||||||
return pmc_usb_mux_dp_hpd(port, state);
|
return pmc_usb_mux_dp_hpd(port, state->data);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -205,6 +260,10 @@ pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state)
|
||||||
u8 cable_speed = TBT_CABLE_SPEED(data->cable_mode);
|
u8 cable_speed = TBT_CABLE_SPEED(data->cable_mode);
|
||||||
struct altmode_req req = { };
|
struct altmode_req req = { };
|
||||||
|
|
||||||
|
if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) ||
|
||||||
|
IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB))
|
||||||
|
return 0;
|
||||||
|
|
||||||
req.usage = PMC_USB_ALT_MODE;
|
req.usage = PMC_USB_ALT_MODE;
|
||||||
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
||||||
req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT;
|
req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT;
|
||||||
|
@ -239,6 +298,10 @@ pmc_usb_mux_usb4(struct pmc_usb_port *port, struct typec_mux_state *state)
|
||||||
struct altmode_req req = { };
|
struct altmode_req req = { };
|
||||||
u8 cable_speed;
|
u8 cable_speed;
|
||||||
|
|
||||||
|
if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) ||
|
||||||
|
IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB))
|
||||||
|
return 0;
|
||||||
|
|
||||||
req.usage = PMC_USB_ALT_MODE;
|
req.usage = PMC_USB_ALT_MODE;
|
||||||
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
||||||
req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT;
|
req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT;
|
||||||
|
@ -273,6 +336,9 @@ static int pmc_usb_mux_safe_state(struct pmc_usb_port *port)
|
||||||
{
|
{
|
||||||
u8 msg;
|
u8 msg;
|
||||||
|
|
||||||
|
if (IOM_PORT_ACTIVITY_IS(port->iom_status, SAFE_MODE))
|
||||||
|
return 0;
|
||||||
|
|
||||||
msg = PMC_USB_SAFE_MODE;
|
msg = PMC_USB_SAFE_MODE;
|
||||||
msg |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
msg |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
||||||
|
|
||||||
|
@ -283,6 +349,9 @@ static int pmc_usb_connect(struct pmc_usb_port *port)
|
||||||
{
|
{
|
||||||
u8 msg[2];
|
u8 msg[2];
|
||||||
|
|
||||||
|
if (port->iom_status & IOM_PORT_STATUS_CONNECTED)
|
||||||
|
return 0;
|
||||||
|
|
||||||
msg[0] = PMC_USB_CONNECT;
|
msg[0] = PMC_USB_CONNECT;
|
||||||
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
||||||
|
|
||||||
|
@ -295,8 +364,16 @@ static int pmc_usb_connect(struct pmc_usb_port *port)
|
||||||
|
|
||||||
static int pmc_usb_disconnect(struct pmc_usb_port *port)
|
static int pmc_usb_disconnect(struct pmc_usb_port *port)
|
||||||
{
|
{
|
||||||
|
struct typec_displayport_data data = { };
|
||||||
u8 msg[2];
|
u8 msg[2];
|
||||||
|
|
||||||
|
if (!(port->iom_status & IOM_PORT_STATUS_CONNECTED))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Clear DisplayPort HPD if it's still asserted. */
|
||||||
|
if (IOM_PORT_HPD_ASSERTED(port->iom_status))
|
||||||
|
pmc_usb_mux_dp_hpd(port, &data);
|
||||||
|
|
||||||
msg[0] = PMC_USB_DISCONNECT;
|
msg[0] = PMC_USB_DISCONNECT;
|
||||||
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT;
|
||||||
|
|
||||||
|
@ -310,6 +387,8 @@ pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
|
||||||
{
|
{
|
||||||
struct pmc_usb_port *port = typec_mux_get_drvdata(mux);
|
struct pmc_usb_port *port = typec_mux_get_drvdata(mux);
|
||||||
|
|
||||||
|
update_port_status(port);
|
||||||
|
|
||||||
if (port->orientation == TYPEC_ORIENTATION_NONE || port->role == USB_ROLE_NONE)
|
if (port->orientation == TYPEC_ORIENTATION_NONE || port->role == USB_ROLE_NONE)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -345,8 +424,7 @@ static int pmc_usb_set_orientation(struct typec_switch *sw,
|
||||||
{
|
{
|
||||||
struct pmc_usb_port *port = typec_switch_get_drvdata(sw);
|
struct pmc_usb_port *port = typec_switch_get_drvdata(sw);
|
||||||
|
|
||||||
if (port->orientation == orientation)
|
update_port_status(port);
|
||||||
return 0;
|
|
||||||
|
|
||||||
port->orientation = orientation;
|
port->orientation = orientation;
|
||||||
|
|
||||||
|
@ -364,8 +442,7 @@ static int pmc_usb_set_role(struct usb_role_switch *sw, enum usb_role role)
|
||||||
{
|
{
|
||||||
struct pmc_usb_port *port = usb_role_switch_get_drvdata(sw);
|
struct pmc_usb_port *port = usb_role_switch_get_drvdata(sw);
|
||||||
|
|
||||||
if (port->role == role)
|
update_port_status(port);
|
||||||
return 0;
|
|
||||||
|
|
||||||
port->role = role;
|
port->role = role;
|
||||||
|
|
||||||
|
@ -450,6 +527,45 @@ static int pmc_usb_register_port(struct pmc_usb *pmc, int index,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int is_memory(struct acpi_resource *res, void *data)
|
||||||
|
{
|
||||||
|
struct resource r;
|
||||||
|
|
||||||
|
return !acpi_dev_resource_memory(res, &r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pmc_usb_probe_iom(struct pmc_usb *pmc)
|
||||||
|
{
|
||||||
|
struct list_head resource_list;
|
||||||
|
struct resource_entry *rentry;
|
||||||
|
struct acpi_device *adev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
adev = acpi_dev_get_first_match_dev("INTC1072", NULL, -1);
|
||||||
|
if (!adev)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&resource_list);
|
||||||
|
ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
rentry = list_first_entry_or_null(&resource_list, struct resource_entry, node);
|
||||||
|
if (rentry)
|
||||||
|
pmc->iom_base = devm_ioremap_resource(pmc->dev, rentry->res);
|
||||||
|
|
||||||
|
acpi_dev_free_resource_list(&resource_list);
|
||||||
|
|
||||||
|
if (!pmc->iom_base) {
|
||||||
|
put_device(&adev->dev);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
pmc->iom_adev = adev;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int pmc_usb_probe(struct platform_device *pdev)
|
static int pmc_usb_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct fwnode_handle *fwnode = NULL;
|
struct fwnode_handle *fwnode = NULL;
|
||||||
|
@ -464,6 +580,12 @@ static int pmc_usb_probe(struct platform_device *pdev)
|
||||||
device_for_each_child_node(&pdev->dev, fwnode)
|
device_for_each_child_node(&pdev->dev, fwnode)
|
||||||
pmc->num_ports++;
|
pmc->num_ports++;
|
||||||
|
|
||||||
|
/* The IOM microcontroller has a limitation of max 4 ports. */
|
||||||
|
if (pmc->num_ports > 4) {
|
||||||
|
dev_err(&pdev->dev, "driver limited to 4 ports\n");
|
||||||
|
return -ERANGE;
|
||||||
|
}
|
||||||
|
|
||||||
pmc->port = devm_kcalloc(&pdev->dev, pmc->num_ports,
|
pmc->port = devm_kcalloc(&pdev->dev, pmc->num_ports,
|
||||||
sizeof(struct pmc_usb_port), GFP_KERNEL);
|
sizeof(struct pmc_usb_port), GFP_KERNEL);
|
||||||
if (!pmc->port)
|
if (!pmc->port)
|
||||||
|
@ -475,6 +597,10 @@ static int pmc_usb_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
pmc->dev = &pdev->dev;
|
pmc->dev = &pdev->dev;
|
||||||
|
|
||||||
|
ret = pmc_usb_probe_iom(pmc);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For every physical USB connector (USB2 and USB3 combo) there is a
|
* For every physical USB connector (USB2 and USB3 combo) there is a
|
||||||
* child ACPI device node under the PMC mux ACPI device object.
|
* child ACPI device node under the PMC mux ACPI device object.
|
||||||
|
@ -499,6 +625,8 @@ static int pmc_usb_probe(struct platform_device *pdev)
|
||||||
typec_mux_unregister(pmc->port[i].typec_mux);
|
typec_mux_unregister(pmc->port[i].typec_mux);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put_device(&pmc->iom_adev->dev);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,6 +640,8 @@ static int pmc_usb_remove(struct platform_device *pdev)
|
||||||
typec_mux_unregister(pmc->port[i].typec_mux);
|
typec_mux_unregister(pmc->port[i].typec_mux);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put_device(&pmc->iom_adev->dev);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue