mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-28 21:33:52 +00:00
cf65598d59
new drivers: - imagination - new driver for Imagination Technologies GPU - xe - new driver for Intel GPUs using core drm concepts core: - add CLOSE_FB ioctl - remove old UMS ioctls - increase max objects to accomodate AMD color mgmt encoder: - create per-encoder debugfs directory edid: - split out drm_eld - SAD helpers - drop edid_firmware module parameter format-helper: - cache format conversion buffers sched: - move from kthread to workqueue - rename some internals - implement dynamic job-flow control gpuvm: - provide more features to handle GEM objects client: - don't acquire module reference displayport: - add mst path property documentation fdinfo: - alignment fix dma-buf: - add fence timestamp helper - add fence deadline support bridge: - transparent aux-bridge for DP/USB-C - lt8912b: add suspend/resume support and power regulator support panel: - edp: AUO B116XTN02, BOE NT116WHM-N21,836X2, NV116WHM-N49 - chromebook panel support - elida-kd35t133: rework pm - powkiddy RK2023 panel - himax-hx8394: drop prepare/unprepare and shutdown logic - BOE BP101WX1-100, Powkiddy X55, Ampire AM8001280G - Evervision VGG644804, SDC ATNA45AF01 - nv3052c: register docs, init sequence fixes, fascontek FS035VG158 - st7701: Anbernic RG-ARC support - r63353 panel controller - Ilitek ILI9805 panel controller - AUO G156HAN04.0 simplefb: - support memory regions - support power domains amdgpu: - add new 64-bit sequence number infrastructure - add AMD specific color management - ACPI WBRF support for RF interference handling - GPUVM updates - RAS updates - DCN 3.5 updates - Rework PCIe link speed handling - Document GPU reset types - DMUB fixes - eDP fixes - NBIO 7.9/7.11 updates - SubVP updates - XGMI PCIe state dumping for aqua vanjaram - GFX11 golden register updates - enable tunnelling on high pri compute amdkfd: - Migrate TLB flushing logic to amdgpu - Trap handler fixes - Fix restore workers handling on suspend/resume - Fix possible memory leak in pqm_uninit() - support import/export of dma-bufs using GEM handles radeon: - fix possible overflows in command buffer checking - check for errors in ring_lock i915: - reorg display code for reuse in xe driver - fdinfo memory stats printing - DP MST bandwidth mgmt improvements - DP panel replay enabling - MTL C20 phy state verification - MTL DP DSC fractional bpp support - Audio fastset support - use dma_fence interfaces instead of i915_sw_fence - Separate gem and display code - AUX register macro refactoring - Separate display module/device parameters - Move display capabilities debugfs under display - Makefile cleanups - Register cleanups - Move display lock inits under display/ - VLV/CHV DPIO PHY register and interface refactoring - DSI VBT sequence refactoring - C10/C20 PHY PLL hardware readout - DPLL code cleanups - Cleanup PXP plane protection checks - Improve display debug msgs - PSR selective fetch fixes/improvements - DP MST fixes - Xe2LPD FBC restrictions removed - DGFX uses direct VBT pin mapping - more MTL WAs - fix MTL eDP bug - eliminate use of kmap_atomic habanalabs: - sysfs entry to identify a device minor id with debugfs path - sysfs entry to expose device module id - add signed device info retrieval through INFO ioctl - add Gaudi2C device support - pcie reset prepare/done hooks msm: - Add support for SDM670, SM8650 - Handle the CFG interconnect to fix the obscure hangs / timeouts - Kconfig fix for QMP dependency - use managed allocators - DPU: SDM670, SM8650 support - DPU: Enable SmartDMA on SM8350 and SM8450 - DP: enable runtime PM support - GPU: add metadata UAPI - GPU: move devcoredumps to GPU device - GPU: convert to drm_exec ivpu: - update FW API - new debugfs file - a new NOP job submission test mode - improve suspend/resume - PM improvements - MMU PT optimizations - firmware profile frequency support - support for uncached buffers - switch to gem shmem helpers - replace kthread with threaded irqs rockchip: - rk3066_hdmi: convert to atomic - vop2: support nv20 and nv30 - rk3588 support mediatek: - use devm_platform_ioremap_resource - stop using iommu_present - MT8188 VDOSYS1 display support panfrost: - PM improvements - improve interrupt handling as poweroff qaic: - allow to run with single MSI - support host/device time sync - switch to persistent DRM devices exynos: - fix potential error pointer dereference - fix wrong error checking - add missing call to drm_atomic_helper_shutdown omapdrm: - dma-fence lockdep annotation fix tidss: - dma-fence lockdep annotation fix - support for AM62A7 v3d: - BCM2712 - rpi5 support - fdinfo + gputop support - uapi for CPU job handling virtio-gpu: - add context debug name -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEEKbZHaGwW9KfbeusDHTzWXnEhr4FAmWeLcQACgkQDHTzWXnE hr54zg//dtPiG9nRA3OeoQh/pTmbFO26uhS8OluLiXhcX/7T/c1e6ck4dA3De5kB wgaqVH6/TFuMgiBbEqZSFuQM6k2X3HLCgHcCRpiz7iGse2GODLtFiUE/E4XFPrSP VhycI64and9XLBmxW87yGdmezVXxo6KZNX4nYabgZ7SD83/2w+ub6rxiAvd0KfSO gFmaOrujOIYBjFYFtKLZIYLH4Jzsy81bP0REBzEnAiWYV5qHdsXfvVgwuOU+3G/B BAVUUf++SU046QeD3HPEuOp3AqgazF4uNHQH5QL0UD2144uGWsk0LA4OZBnU0qhd oM4Oxu9V+TXvRfYhHwiQKeVleifcZBijndqiF7rlrTnNqS4YYOCPxuXzMlZO9aEJ 6wQL/0JX8d5G6lXsweoBzNC76jeU/gspd1DvyaTFt7I8l8YqWvR5V8l8KRf2s14R +CwwujoqMMVmhZ4WhB+FgZTiWw5PaWoMM9ijVFOv8QhXOz21rj718NPdBspvdJK3 Lo3obSO5p4lqgkMEuINBEXzkHjcSyOmMe1fG4Et8Wr+IrEBr1gfG9E4Twr+3/k3s 9Ok9nOPykbYmt4gfJp/RDNCWBr8QGZKznP6Nq8EFfIqhEkXOHQo9wtsofVUhyW7P qEkCYcYkRa89KFp4Lep6lgDT5O7I+32eRmbRg716qRm9nn3Vj3Y= =nuw0 -----END PGP SIGNATURE----- Merge tag 'drm-next-2024-01-10' of git://anongit.freedesktop.org/drm/drm Pull drm updates from Dave Airlie: "This contains two major new drivers: - imagination is a first driver for Imagination Technologies devices, it only covers very specific devices, but there is hope to grow it - xe is a reboot of the i915 GPU (shares display) side using a more upstream focused development model, and trying to maximise code sharing. It's not enabled for any hw by default, and will hopefully get switched on for Intel's Lunarlake. This also drops a bunch of the old UMS ioctls. It's been dead long enough. amdgpu has a bunch of new color management code that is being used in the Steam Deck. amdgpu also has a new ACPI WBRF interaction to help avoid radio interference. Otherwise it's the usual lots of changes in lots of places. Detailed summary: new drivers: - imagination - new driver for Imagination Technologies GPU - xe - new driver for Intel GPUs using core drm concepts core: - add CLOSE_FB ioctl - remove old UMS ioctls - increase max objects to accomodate AMD color mgmt encoder: - create per-encoder debugfs directory edid: - split out drm_eld - SAD helpers - drop edid_firmware module parameter format-helper: - cache format conversion buffers sched: - move from kthread to workqueue - rename some internals - implement dynamic job-flow control gpuvm: - provide more features to handle GEM objects client: - don't acquire module reference displayport: - add mst path property documentation fdinfo: - alignment fix dma-buf: - add fence timestamp helper - add fence deadline support bridge: - transparent aux-bridge for DP/USB-C - lt8912b: add suspend/resume support and power regulator support panel: - edp: AUO B116XTN02, BOE NT116WHM-N21,836X2, NV116WHM-N49 - chromebook panel support - elida-kd35t133: rework pm - powkiddy RK2023 panel - himax-hx8394: drop prepare/unprepare and shutdown logic - BOE BP101WX1-100, Powkiddy X55, Ampire AM8001280G - Evervision VGG644804, SDC ATNA45AF01 - nv3052c: register docs, init sequence fixes, fascontek FS035VG158 - st7701: Anbernic RG-ARC support - r63353 panel controller - Ilitek ILI9805 panel controller - AUO G156HAN04.0 simplefb: - support memory regions - support power domains amdgpu: - add new 64-bit sequence number infrastructure - add AMD specific color management - ACPI WBRF support for RF interference handling - GPUVM updates - RAS updates - DCN 3.5 updates - Rework PCIe link speed handling - Document GPU reset types - DMUB fixes - eDP fixes - NBIO 7.9/7.11 updates - SubVP updates - XGMI PCIe state dumping for aqua vanjaram - GFX11 golden register updates - enable tunnelling on high pri compute amdkfd: - Migrate TLB flushing logic to amdgpu - Trap handler fixes - Fix restore workers handling on suspend/resume - Fix possible memory leak in pqm_uninit() - support import/export of dma-bufs using GEM handles radeon: - fix possible overflows in command buffer checking - check for errors in ring_lock i915: - reorg display code for reuse in xe driver - fdinfo memory stats printing - DP MST bandwidth mgmt improvements - DP panel replay enabling - MTL C20 phy state verification - MTL DP DSC fractional bpp support - Audio fastset support - use dma_fence interfaces instead of i915_sw_fence - Separate gem and display code - AUX register macro refactoring - Separate display module/device parameters - Move display capabilities debugfs under display - Makefile cleanups - Register cleanups - Move display lock inits under display/ - VLV/CHV DPIO PHY register and interface refactoring - DSI VBT sequence refactoring - C10/C20 PHY PLL hardware readout - DPLL code cleanups - Cleanup PXP plane protection checks - Improve display debug msgs - PSR selective fetch fixes/improvements - DP MST fixes - Xe2LPD FBC restrictions removed - DGFX uses direct VBT pin mapping - more MTL WAs - fix MTL eDP bug - eliminate use of kmap_atomic habanalabs: - sysfs entry to identify a device minor id with debugfs path - sysfs entry to expose device module id - add signed device info retrieval through INFO ioctl - add Gaudi2C device support - pcie reset prepare/done hooks msm: - Add support for SDM670, SM8650 - Handle the CFG interconnect to fix the obscure hangs / timeouts - Kconfig fix for QMP dependency - use managed allocators - DPU: SDM670, SM8650 support - DPU: Enable SmartDMA on SM8350 and SM8450 - DP: enable runtime PM support - GPU: add metadata UAPI - GPU: move devcoredumps to GPU device - GPU: convert to drm_exec ivpu: - update FW API - new debugfs file - a new NOP job submission test mode - improve suspend/resume - PM improvements - MMU PT optimizations - firmware profile frequency support - support for uncached buffers - switch to gem shmem helpers - replace kthread with threaded irqs rockchip: - rk3066_hdmi: convert to atomic - vop2: support nv20 and nv30 - rk3588 support mediatek: - use devm_platform_ioremap_resource - stop using iommu_present - MT8188 VDOSYS1 display support panfrost: - PM improvements - improve interrupt handling as poweroff qaic: - allow to run with single MSI - support host/device time sync - switch to persistent DRM devices exynos: - fix potential error pointer dereference - fix wrong error checking - add missing call to drm_atomic_helper_shutdown omapdrm: - dma-fence lockdep annotation fix tidss: - dma-fence lockdep annotation fix - support for AM62A7 v3d: - BCM2712 - rpi5 support - fdinfo + gputop support - uapi for CPU job handling virtio-gpu: - add context debug name" * tag 'drm-next-2024-01-10' of git://anongit.freedesktop.org/drm/drm: (2340 commits) drm/amd/display: Allow z8/z10 from driver drm/amd/display: fix bandwidth validation failure on DCN 2.1 drm/amdgpu: apply the RV2 system aperture fix to RN/CZN as well drm/amd/display: Move fixpt_from_s3132 to amdgpu_dm drm/amd/display: Fix recent checkpatch errors in amdgpu_dm Revert "drm/amdkfd: Relocate TBA/TMA to opposite side of VM hole" drm/amd/display: avoid stringop-overflow warnings for dp_decide_lane_settings() drm/amd/display: Fix power_helpers.c codestyle drm/amd/display: Fix hdcp_log.h codestyle drm/amd/display: Fix hdcp2_execution.c codestyle drm/amd/display: Fix hdcp_psp.h codestyle drm/amd/display: Fix freesync.c codestyle drm/amd/display: Fix hdcp_psp.c codestyle drm/amd/display: Fix hdcp1_execution.c codestyle drm/amd/pm/smu7: fix a memleak in smu7_hwmgr_backend_init drm/amdkfd: Fix iterator used outside loop in 'kfd_add_peer_prop()' drm/amdgpu: Drop 'fence' check in 'to_amdgpu_amdkfd_fence()' drm/amdkfd: Confirm list is non-empty before utilizing list_first_entry in kfd_topology.c drm/amdgpu: Fix '*fw' from request_firmware() not released in 'amdgpu_ucode_request()' drm/amdgpu: Fix variable 'mca_funcs' dereferenced before NULL check in 'amdgpu_mca_smu_get_mca_entry()' ...
536 lines
14 KiB
C
536 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2022, Linaro Ltd
|
|
*/
|
|
#include <linux/auxiliary_bus.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/property.h>
|
|
#include <linux/soc/qcom/pdr.h>
|
|
#include <drm/bridge/aux-bridge.h>
|
|
|
|
#include <linux/usb/typec_altmode.h>
|
|
#include <linux/usb/typec_dp.h>
|
|
#include <linux/usb/typec_mux.h>
|
|
#include <linux/usb/typec_retimer.h>
|
|
|
|
#include <linux/soc/qcom/pmic_glink.h>
|
|
|
|
#define PMIC_GLINK_MAX_PORTS 2
|
|
|
|
#define USBC_SC8180X_NOTIFY_IND 0x13
|
|
#define USBC_CMD_WRITE_REQ 0x15
|
|
#define USBC_NOTIFY_IND 0x16
|
|
|
|
#define ALTMODE_PAN_EN 0x10
|
|
#define ALTMODE_PAN_ACK 0x11
|
|
|
|
struct usbc_write_req {
|
|
struct pmic_glink_hdr hdr;
|
|
__le32 cmd;
|
|
__le32 arg;
|
|
__le32 reserved;
|
|
};
|
|
|
|
#define NOTIFY_PAYLOAD_SIZE 16
|
|
struct usbc_notify {
|
|
struct pmic_glink_hdr hdr;
|
|
char payload[NOTIFY_PAYLOAD_SIZE];
|
|
u32 reserved;
|
|
};
|
|
|
|
struct usbc_sc8180x_notify {
|
|
struct pmic_glink_hdr hdr;
|
|
__le32 notification;
|
|
__le32 reserved[2];
|
|
};
|
|
|
|
enum pmic_glink_altmode_pin_assignment {
|
|
DPAM_HPD_OUT,
|
|
DPAM_HPD_A,
|
|
DPAM_HPD_B,
|
|
DPAM_HPD_C,
|
|
DPAM_HPD_D,
|
|
DPAM_HPD_E,
|
|
DPAM_HPD_F,
|
|
};
|
|
|
|
struct pmic_glink_altmode;
|
|
|
|
#define work_to_altmode_port(w) container_of((w), struct pmic_glink_altmode_port, work)
|
|
|
|
struct pmic_glink_altmode_port {
|
|
struct pmic_glink_altmode *altmode;
|
|
unsigned int index;
|
|
|
|
struct typec_switch *typec_switch;
|
|
struct typec_mux *typec_mux;
|
|
struct typec_mux_state state;
|
|
struct typec_retimer *typec_retimer;
|
|
struct typec_retimer_state retimer_state;
|
|
struct typec_altmode dp_alt;
|
|
|
|
struct work_struct work;
|
|
|
|
struct device *bridge;
|
|
|
|
enum typec_orientation orientation;
|
|
u16 svid;
|
|
u8 dp_data;
|
|
u8 mode;
|
|
u8 hpd_state;
|
|
u8 hpd_irq;
|
|
};
|
|
|
|
#define work_to_altmode(w) container_of((w), struct pmic_glink_altmode, enable_work)
|
|
|
|
struct pmic_glink_altmode {
|
|
struct device *dev;
|
|
|
|
unsigned int owner_id;
|
|
|
|
/* To synchronize WRITE_REQ acks */
|
|
struct mutex lock;
|
|
|
|
struct completion pan_ack;
|
|
struct pmic_glink_client *client;
|
|
|
|
struct work_struct enable_work;
|
|
|
|
struct pmic_glink_altmode_port ports[PMIC_GLINK_MAX_PORTS];
|
|
};
|
|
|
|
static int pmic_glink_altmode_request(struct pmic_glink_altmode *altmode, u32 cmd, u32 arg)
|
|
{
|
|
struct usbc_write_req req = {};
|
|
unsigned long left;
|
|
int ret;
|
|
|
|
/*
|
|
* The USBC_CMD_WRITE_REQ ack doesn't identify the request, so wait for
|
|
* one ack at a time.
|
|
*/
|
|
mutex_lock(&altmode->lock);
|
|
|
|
req.hdr.owner = cpu_to_le32(altmode->owner_id);
|
|
req.hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP);
|
|
req.hdr.opcode = cpu_to_le32(USBC_CMD_WRITE_REQ);
|
|
req.cmd = cpu_to_le32(cmd);
|
|
req.arg = cpu_to_le32(arg);
|
|
|
|
ret = pmic_glink_send(altmode->client, &req, sizeof(req));
|
|
if (ret) {
|
|
dev_err(altmode->dev, "failed to send altmode request: %#x (%d)\n", cmd, ret);
|
|
goto out_unlock;
|
|
}
|
|
|
|
left = wait_for_completion_timeout(&altmode->pan_ack, 5 * HZ);
|
|
if (!left) {
|
|
dev_err(altmode->dev, "timeout waiting for altmode request ack for: %#x\n", cmd);
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
|
|
out_unlock:
|
|
mutex_unlock(&altmode->lock);
|
|
return ret;
|
|
}
|
|
|
|
static void pmic_glink_altmode_enable_dp(struct pmic_glink_altmode *altmode,
|
|
struct pmic_glink_altmode_port *port,
|
|
u8 mode, bool hpd_state,
|
|
bool hpd_irq)
|
|
{
|
|
struct typec_displayport_data dp_data = {};
|
|
int ret;
|
|
|
|
dp_data.status = DP_STATUS_ENABLED;
|
|
if (hpd_state)
|
|
dp_data.status |= DP_STATUS_HPD_STATE;
|
|
if (hpd_irq)
|
|
dp_data.status |= DP_STATUS_IRQ_HPD;
|
|
dp_data.conf = DP_CONF_SET_PIN_ASSIGN(mode);
|
|
|
|
port->state.alt = &port->dp_alt;
|
|
port->state.data = &dp_data;
|
|
port->state.mode = TYPEC_MODAL_STATE(mode);
|
|
|
|
ret = typec_mux_set(port->typec_mux, &port->state);
|
|
if (ret)
|
|
dev_err(altmode->dev, "failed to switch mux to DP: %d\n", ret);
|
|
|
|
port->retimer_state.alt = &port->dp_alt;
|
|
port->retimer_state.data = &dp_data;
|
|
port->retimer_state.mode = TYPEC_MODAL_STATE(mode);
|
|
|
|
ret = typec_retimer_set(port->typec_retimer, &port->retimer_state);
|
|
if (ret)
|
|
dev_err(altmode->dev, "failed to setup retimer to DP: %d\n", ret);
|
|
}
|
|
|
|
static void pmic_glink_altmode_enable_usb(struct pmic_glink_altmode *altmode,
|
|
struct pmic_glink_altmode_port *port)
|
|
{
|
|
int ret;
|
|
|
|
port->state.alt = NULL;
|
|
port->state.data = NULL;
|
|
port->state.mode = TYPEC_STATE_USB;
|
|
|
|
ret = typec_mux_set(port->typec_mux, &port->state);
|
|
if (ret)
|
|
dev_err(altmode->dev, "failed to switch mux to USB: %d\n", ret);
|
|
|
|
port->retimer_state.alt = NULL;
|
|
port->retimer_state.data = NULL;
|
|
port->retimer_state.mode = TYPEC_STATE_USB;
|
|
|
|
ret = typec_retimer_set(port->typec_retimer, &port->retimer_state);
|
|
if (ret)
|
|
dev_err(altmode->dev, "failed to setup retimer to USB: %d\n", ret);
|
|
}
|
|
|
|
static void pmic_glink_altmode_safe(struct pmic_glink_altmode *altmode,
|
|
struct pmic_glink_altmode_port *port)
|
|
{
|
|
int ret;
|
|
|
|
port->state.alt = NULL;
|
|
port->state.data = NULL;
|
|
port->state.mode = TYPEC_STATE_SAFE;
|
|
|
|
ret = typec_mux_set(port->typec_mux, &port->state);
|
|
if (ret)
|
|
dev_err(altmode->dev, "failed to switch mux to safe mode: %d\n", ret);
|
|
|
|
port->retimer_state.alt = NULL;
|
|
port->retimer_state.data = NULL;
|
|
port->retimer_state.mode = TYPEC_STATE_SAFE;
|
|
|
|
ret = typec_retimer_set(port->typec_retimer, &port->retimer_state);
|
|
if (ret)
|
|
dev_err(altmode->dev, "failed to setup retimer to USB: %d\n", ret);
|
|
}
|
|
|
|
static void pmic_glink_altmode_worker(struct work_struct *work)
|
|
{
|
|
struct pmic_glink_altmode_port *alt_port = work_to_altmode_port(work);
|
|
struct pmic_glink_altmode *altmode = alt_port->altmode;
|
|
|
|
typec_switch_set(alt_port->typec_switch, alt_port->orientation);
|
|
|
|
if (alt_port->svid == USB_TYPEC_DP_SID && alt_port->mode == 0xff)
|
|
pmic_glink_altmode_safe(altmode, alt_port);
|
|
else if (alt_port->svid == USB_TYPEC_DP_SID)
|
|
pmic_glink_altmode_enable_dp(altmode, alt_port, alt_port->mode,
|
|
alt_port->hpd_state, alt_port->hpd_irq);
|
|
else
|
|
pmic_glink_altmode_enable_usb(altmode, alt_port);
|
|
|
|
drm_aux_hpd_bridge_notify(alt_port->bridge,
|
|
alt_port->hpd_state ?
|
|
connector_status_connected :
|
|
connector_status_disconnected);
|
|
|
|
pmic_glink_altmode_request(altmode, ALTMODE_PAN_ACK, alt_port->index);
|
|
}
|
|
|
|
static enum typec_orientation pmic_glink_altmode_orientation(unsigned int orientation)
|
|
{
|
|
if (orientation == 0)
|
|
return TYPEC_ORIENTATION_NORMAL;
|
|
else if (orientation == 1)
|
|
return TYPEC_ORIENTATION_REVERSE;
|
|
else
|
|
return TYPEC_ORIENTATION_NONE;
|
|
}
|
|
|
|
#define SC8180X_PORT_MASK 0x000000ff
|
|
#define SC8180X_ORIENTATION_MASK 0x0000ff00
|
|
#define SC8180X_MUX_MASK 0x00ff0000
|
|
#define SC8180X_MODE_MASK 0x3f000000
|
|
#define SC8180X_HPD_STATE_MASK 0x40000000
|
|
#define SC8180X_HPD_IRQ_MASK 0x80000000
|
|
|
|
static void pmic_glink_altmode_sc8180xp_notify(struct pmic_glink_altmode *altmode,
|
|
const void *data, size_t len)
|
|
{
|
|
struct pmic_glink_altmode_port *alt_port;
|
|
const struct usbc_sc8180x_notify *msg;
|
|
u32 notification;
|
|
u8 orientation;
|
|
u8 hpd_state;
|
|
u8 hpd_irq;
|
|
u16 svid;
|
|
u8 port;
|
|
u8 mode;
|
|
u8 mux;
|
|
|
|
if (len != sizeof(*msg)) {
|
|
dev_warn(altmode->dev, "invalid length of USBC_NOTIFY indication: %zd\n", len);
|
|
return;
|
|
}
|
|
|
|
msg = data;
|
|
notification = le32_to_cpu(msg->notification);
|
|
port = FIELD_GET(SC8180X_PORT_MASK, notification);
|
|
orientation = FIELD_GET(SC8180X_ORIENTATION_MASK, notification);
|
|
mux = FIELD_GET(SC8180X_MUX_MASK, notification);
|
|
mode = FIELD_GET(SC8180X_MODE_MASK, notification);
|
|
hpd_state = FIELD_GET(SC8180X_HPD_STATE_MASK, notification);
|
|
hpd_irq = FIELD_GET(SC8180X_HPD_IRQ_MASK, notification);
|
|
|
|
svid = mux == 2 ? USB_TYPEC_DP_SID : 0;
|
|
|
|
if (port >= ARRAY_SIZE(altmode->ports) || !altmode->ports[port].altmode) {
|
|
dev_dbg(altmode->dev, "notification on undefined port %d\n", port);
|
|
return;
|
|
}
|
|
|
|
alt_port = &altmode->ports[port];
|
|
alt_port->orientation = pmic_glink_altmode_orientation(orientation);
|
|
alt_port->svid = svid;
|
|
alt_port->mode = mode;
|
|
alt_port->hpd_state = hpd_state;
|
|
alt_port->hpd_irq = hpd_irq;
|
|
schedule_work(&alt_port->work);
|
|
}
|
|
|
|
#define SC8280XP_DPAM_MASK 0x3f
|
|
#define SC8280XP_HPD_STATE_MASK BIT(6)
|
|
#define SC8280XP_HPD_IRQ_MASK BIT(7)
|
|
|
|
static void pmic_glink_altmode_sc8280xp_notify(struct pmic_glink_altmode *altmode,
|
|
u16 svid, const void *data, size_t len)
|
|
{
|
|
struct pmic_glink_altmode_port *alt_port;
|
|
const struct usbc_notify *notify;
|
|
u8 orientation;
|
|
u8 hpd_state;
|
|
u8 hpd_irq;
|
|
u8 mode;
|
|
u8 port;
|
|
|
|
if (len != sizeof(*notify)) {
|
|
dev_warn(altmode->dev, "invalid length USBC_NOTIFY_IND: %zd\n",
|
|
len);
|
|
return;
|
|
}
|
|
|
|
notify = data;
|
|
|
|
port = notify->payload[0];
|
|
orientation = notify->payload[1];
|
|
mode = FIELD_GET(SC8280XP_DPAM_MASK, notify->payload[8]) - DPAM_HPD_A;
|
|
hpd_state = FIELD_GET(SC8280XP_HPD_STATE_MASK, notify->payload[8]);
|
|
hpd_irq = FIELD_GET(SC8280XP_HPD_IRQ_MASK, notify->payload[8]);
|
|
|
|
if (port >= ARRAY_SIZE(altmode->ports) || !altmode->ports[port].altmode) {
|
|
dev_dbg(altmode->dev, "notification on undefined port %d\n", port);
|
|
return;
|
|
}
|
|
|
|
alt_port = &altmode->ports[port];
|
|
alt_port->orientation = pmic_glink_altmode_orientation(orientation);
|
|
alt_port->svid = svid;
|
|
alt_port->mode = mode;
|
|
alt_port->hpd_state = hpd_state;
|
|
alt_port->hpd_irq = hpd_irq;
|
|
schedule_work(&alt_port->work);
|
|
}
|
|
|
|
static void pmic_glink_altmode_callback(const void *data, size_t len, void *priv)
|
|
{
|
|
struct pmic_glink_altmode *altmode = priv;
|
|
const struct pmic_glink_hdr *hdr = data;
|
|
u16 opcode;
|
|
u16 svid;
|
|
|
|
opcode = le32_to_cpu(hdr->opcode) & 0xff;
|
|
svid = le32_to_cpu(hdr->opcode) >> 16;
|
|
|
|
switch (opcode) {
|
|
case USBC_CMD_WRITE_REQ:
|
|
complete(&altmode->pan_ack);
|
|
break;
|
|
case USBC_NOTIFY_IND:
|
|
pmic_glink_altmode_sc8280xp_notify(altmode, svid, data, len);
|
|
break;
|
|
case USBC_SC8180X_NOTIFY_IND:
|
|
pmic_glink_altmode_sc8180xp_notify(altmode, data, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void pmic_glink_altmode_put_retimer(void *data)
|
|
{
|
|
typec_retimer_put(data);
|
|
}
|
|
|
|
static void pmic_glink_altmode_put_mux(void *data)
|
|
{
|
|
typec_mux_put(data);
|
|
}
|
|
|
|
static void pmic_glink_altmode_put_switch(void *data)
|
|
{
|
|
typec_switch_put(data);
|
|
}
|
|
|
|
static void pmic_glink_altmode_enable_worker(struct work_struct *work)
|
|
{
|
|
struct pmic_glink_altmode *altmode = work_to_altmode(work);
|
|
int ret;
|
|
|
|
ret = pmic_glink_altmode_request(altmode, ALTMODE_PAN_EN, 0);
|
|
if (ret)
|
|
dev_err(altmode->dev, "failed to request altmode notifications: %d\n", ret);
|
|
}
|
|
|
|
static void pmic_glink_altmode_pdr_notify(void *priv, int state)
|
|
{
|
|
struct pmic_glink_altmode *altmode = priv;
|
|
|
|
if (state == SERVREG_SERVICE_STATE_UP)
|
|
schedule_work(&altmode->enable_work);
|
|
}
|
|
|
|
static const struct of_device_id pmic_glink_altmode_of_quirks[] = {
|
|
{ .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)PMIC_GLINK_OWNER_USBC },
|
|
{}
|
|
};
|
|
|
|
static int pmic_glink_altmode_probe(struct auxiliary_device *adev,
|
|
const struct auxiliary_device_id *id)
|
|
{
|
|
struct pmic_glink_altmode_port *alt_port;
|
|
struct pmic_glink_altmode *altmode;
|
|
const struct of_device_id *match;
|
|
struct fwnode_handle *fwnode;
|
|
struct device *dev = &adev->dev;
|
|
u32 port;
|
|
int ret;
|
|
|
|
altmode = devm_kzalloc(dev, sizeof(*altmode), GFP_KERNEL);
|
|
if (!altmode)
|
|
return -ENOMEM;
|
|
|
|
altmode->dev = dev;
|
|
|
|
match = of_match_device(pmic_glink_altmode_of_quirks, dev->parent);
|
|
if (match)
|
|
altmode->owner_id = (unsigned long)match->data;
|
|
else
|
|
altmode->owner_id = PMIC_GLINK_OWNER_USBC_PAN;
|
|
|
|
INIT_WORK(&altmode->enable_work, pmic_glink_altmode_enable_worker);
|
|
init_completion(&altmode->pan_ack);
|
|
mutex_init(&altmode->lock);
|
|
|
|
device_for_each_child_node(dev, fwnode) {
|
|
ret = fwnode_property_read_u32(fwnode, "reg", &port);
|
|
if (ret < 0) {
|
|
dev_err(dev, "missing reg property of %pOFn\n", fwnode);
|
|
fwnode_handle_put(fwnode);
|
|
return ret;
|
|
}
|
|
|
|
if (port >= ARRAY_SIZE(altmode->ports)) {
|
|
dev_warn(dev, "invalid connector number, ignoring\n");
|
|
continue;
|
|
}
|
|
|
|
if (altmode->ports[port].altmode) {
|
|
dev_err(dev, "multiple connector definition for port %u\n", port);
|
|
fwnode_handle_put(fwnode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
alt_port = &altmode->ports[port];
|
|
alt_port->altmode = altmode;
|
|
alt_port->index = port;
|
|
INIT_WORK(&alt_port->work, pmic_glink_altmode_worker);
|
|
|
|
alt_port->bridge = drm_dp_hpd_bridge_register(dev, to_of_node(fwnode));
|
|
if (IS_ERR(alt_port->bridge)) {
|
|
fwnode_handle_put(fwnode);
|
|
return PTR_ERR(alt_port->bridge);
|
|
}
|
|
|
|
alt_port->dp_alt.svid = USB_TYPEC_DP_SID;
|
|
alt_port->dp_alt.mode = USB_TYPEC_DP_MODE;
|
|
alt_port->dp_alt.active = 1;
|
|
|
|
alt_port->typec_mux = fwnode_typec_mux_get(fwnode);
|
|
if (IS_ERR(alt_port->typec_mux)) {
|
|
fwnode_handle_put(fwnode);
|
|
return dev_err_probe(dev, PTR_ERR(alt_port->typec_mux),
|
|
"failed to acquire mode-switch for port: %d\n",
|
|
port);
|
|
}
|
|
|
|
ret = devm_add_action_or_reset(dev, pmic_glink_altmode_put_mux,
|
|
alt_port->typec_mux);
|
|
if (ret) {
|
|
fwnode_handle_put(fwnode);
|
|
return ret;
|
|
}
|
|
|
|
alt_port->typec_retimer = fwnode_typec_retimer_get(fwnode);
|
|
if (IS_ERR(alt_port->typec_retimer)) {
|
|
fwnode_handle_put(fwnode);
|
|
return dev_err_probe(dev, PTR_ERR(alt_port->typec_retimer),
|
|
"failed to acquire retimer-switch for port: %d\n",
|
|
port);
|
|
}
|
|
|
|
ret = devm_add_action_or_reset(dev, pmic_glink_altmode_put_retimer,
|
|
alt_port->typec_retimer);
|
|
if (ret) {
|
|
fwnode_handle_put(fwnode);
|
|
return ret;
|
|
}
|
|
|
|
alt_port->typec_switch = fwnode_typec_switch_get(fwnode);
|
|
if (IS_ERR(alt_port->typec_switch)) {
|
|
fwnode_handle_put(fwnode);
|
|
return dev_err_probe(dev, PTR_ERR(alt_port->typec_switch),
|
|
"failed to acquire orientation-switch for port: %d\n",
|
|
port);
|
|
}
|
|
|
|
ret = devm_add_action_or_reset(dev, pmic_glink_altmode_put_switch,
|
|
alt_port->typec_switch);
|
|
if (ret) {
|
|
fwnode_handle_put(fwnode);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
altmode->client = devm_pmic_glink_register_client(dev,
|
|
altmode->owner_id,
|
|
pmic_glink_altmode_callback,
|
|
pmic_glink_altmode_pdr_notify,
|
|
altmode);
|
|
return PTR_ERR_OR_ZERO(altmode->client);
|
|
}
|
|
|
|
static const struct auxiliary_device_id pmic_glink_altmode_id_table[] = {
|
|
{ .name = "pmic_glink.altmode", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(auxiliary, pmic_glink_altmode_id_table);
|
|
|
|
static struct auxiliary_driver pmic_glink_altmode_driver = {
|
|
.name = "pmic_glink_altmode",
|
|
.probe = pmic_glink_altmode_probe,
|
|
.id_table = pmic_glink_altmode_id_table,
|
|
};
|
|
|
|
module_auxiliary_driver(pmic_glink_altmode_driver);
|
|
|
|
MODULE_DESCRIPTION("Qualcomm PMIC GLINK Altmode driver");
|
|
MODULE_LICENSE("GPL");
|