Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
9b4a1915
Commit
9b4a1915
authored
Feb 04, 2023
by
Bjorn Andersson
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '20230201041853.1934355-1-quic_bjorande@quicinc.com' into drivers-for-6.3
parents
c5d52d7b
080b4e24
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
958 additions
and
0 deletions
+958
-0
Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml
...ntation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml
+95
-0
drivers/soc/qcom/Kconfig
drivers/soc/qcom/Kconfig
+15
-0
drivers/soc/qcom/Makefile
drivers/soc/qcom/Makefile
+2
-0
drivers/soc/qcom/pmic_glink.c
drivers/soc/qcom/pmic_glink.c
+336
-0
drivers/soc/qcom/pmic_glink_altmode.c
drivers/soc/qcom/pmic_glink_altmode.c
+478
-0
include/linux/soc/qcom/pmic_glink.h
include/linux/soc/qcom/pmic_glink.h
+32
-0
No files found.
Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml
0 → 100644
View file @
9b4a1915
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML
1.2
---
$id
:
http://devicetree.org/schemas/soc/qcom/qcom,pmic-glink.yaml#
$schema
:
http://devicetree.org/meta-schemas/core.yaml#
title
:
Qualcomm PMIC GLINK firmware interface for battery management, USB
Type-C and other things.
maintainers
:
-
Bjorn Andersson <andersson@kernel.org>
description
:
The PMIC GLINK service, running on a coprocessor on some modern Qualcomm
platforms and implement USB Type-C handling and battery management. This
binding describes the component in the OS used to communicate with the
firmware and connect it's resources to those described in the Devicetree,
particularly the USB Type-C controllers relationship with USB and DisplayPort
components.
properties
:
compatible
:
items
:
-
enum
:
-
qcom,sc8180x-pmic-glink
-
qcom,sc8280xp-pmic-glink
-
qcom,sm8350-pmic-glink
-
const
:
qcom,pmic-glink
'
#address-cells'
:
const
:
1
'
#size-cells'
:
const
:
0
patternProperties
:
'
^connector@\d$'
:
$ref
:
/schemas/connector/usb-connector.yaml#
properties
:
reg
:
true
required
:
-
reg
unevaluatedProperties
:
false
required
:
-
compatible
additionalProperties
:
false
examples
:
-
|+
pmic-glink {
compatible = "qcom,sc8280xp-pmic-glink", "qcom,pmic-glink";
#address-cells = <1>;
#size-cells = <0>;
connector@0 {
compatible = "usb-c-connector";
reg = <0>;
power-role = "dual";
data-role = "dual";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
endpoint {
remote-endpoint = <&usb_role>;
};
};
port@1 {
reg = <1>;
endpoint {
remote-endpoint = <&ss_phy_out>;
};
};
port@2 {
reg = <2>;
endpoint {
remote-endpoint = <&sbu_mux>;
};
};
};
};
};
...
drivers/soc/qcom/Kconfig
View file @
9b4a1915
...
...
@@ -92,6 +92,21 @@ config QCOM_PDR_HELPERS
tristate
select QCOM_QMI_HELPERS
config QCOM_PMIC_GLINK
tristate "Qualcomm PMIC GLINK driver"
depends on RPMSG
depends on TYPEC
depends on DRM
select AUXILIARY_BUS
select QCOM_PDR_HELPERS
help
The Qualcomm PMIC GLINK driver provides access, over GLINK, to the
USB and battery firmware running on one of the coprocessors in
several modern Qualcomm platforms.
Say yes here to support USB-C and battery status on modern Qualcomm
platforms.
config QCOM_QMI_HELPERS
tristate
depends on NET
...
...
drivers/soc/qcom/Makefile
View file @
9b4a1915
...
...
@@ -8,6 +8,8 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
obj-$(CONFIG_QCOM_MDT_LOADER)
+=
mdt_loader.o
obj-$(CONFIG_QCOM_OCMEM)
+=
ocmem.o
obj-$(CONFIG_QCOM_PDR_HELPERS)
+=
pdr_interface.o
obj-$(CONFIG_QCOM_PMIC_GLINK)
+=
pmic_glink.o
obj-$(CONFIG_QCOM_PMIC_GLINK)
+=
pmic_glink_altmode.o
obj-$(CONFIG_QCOM_QMI_HELPERS)
+=
qmi_helpers.o
qmi_helpers-y
+=
qmi_encdec.o qmi_interface.o
obj-$(CONFIG_QCOM_RAMP_CTRL)
+=
ramp_controller.o
...
...
drivers/soc/qcom/pmic_glink.c
0 → 100644
View file @
9b4a1915
// 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/module.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/soc/qcom/pdr.h>
#include <linux/soc/qcom/pmic_glink.h>
struct
pmic_glink
{
struct
device
*
dev
;
struct
pdr_handle
*
pdr
;
struct
rpmsg_endpoint
*
ept
;
struct
auxiliary_device
altmode_aux
;
struct
auxiliary_device
ps_aux
;
struct
auxiliary_device
ucsi_aux
;
/* serializing client_state and pdr_state updates */
struct
mutex
state_lock
;
unsigned
int
client_state
;
unsigned
int
pdr_state
;
/* serializing clients list updates */
struct
mutex
client_lock
;
struct
list_head
clients
;
};
static
struct
pmic_glink
*
__pmic_glink
;
static
DEFINE_MUTEX
(
__pmic_glink_lock
);
struct
pmic_glink_client
{
struct
list_head
node
;
struct
pmic_glink
*
pg
;
unsigned
int
id
;
void
(
*
cb
)(
const
void
*
data
,
size_t
len
,
void
*
priv
);
void
(
*
pdr_notify
)(
void
*
priv
,
int
state
);
void
*
priv
;
};
static
void
_devm_pmic_glink_release_client
(
struct
device
*
dev
,
void
*
res
)
{
struct
pmic_glink_client
*
client
=
(
struct
pmic_glink_client
*
)
res
;
struct
pmic_glink
*
pg
=
client
->
pg
;
mutex_lock
(
&
pg
->
client_lock
);
list_del
(
&
client
->
node
);
mutex_unlock
(
&
pg
->
client_lock
);
}
struct
pmic_glink_client
*
devm_pmic_glink_register_client
(
struct
device
*
dev
,
unsigned
int
id
,
void
(
*
cb
)(
const
void
*
,
size_t
,
void
*
),
void
(
*
pdr
)(
void
*
,
int
),
void
*
priv
)
{
struct
pmic_glink_client
*
client
;
struct
pmic_glink
*
pg
=
dev_get_drvdata
(
dev
->
parent
);
client
=
devres_alloc
(
_devm_pmic_glink_release_client
,
sizeof
(
*
client
),
GFP_KERNEL
);
if
(
!
client
)
return
ERR_PTR
(
-
ENOMEM
);
client
->
pg
=
pg
;
client
->
id
=
id
;
client
->
cb
=
cb
;
client
->
pdr_notify
=
pdr
;
client
->
priv
=
priv
;
mutex_lock
(
&
pg
->
client_lock
);
list_add
(
&
client
->
node
,
&
pg
->
clients
);
mutex_unlock
(
&
pg
->
client_lock
);
devres_add
(
dev
,
client
);
return
client
;
}
EXPORT_SYMBOL_GPL
(
devm_pmic_glink_register_client
);
int
pmic_glink_send
(
struct
pmic_glink_client
*
client
,
void
*
data
,
size_t
len
)
{
struct
pmic_glink
*
pg
=
client
->
pg
;
return
rpmsg_send
(
pg
->
ept
,
data
,
len
);
}
EXPORT_SYMBOL_GPL
(
pmic_glink_send
);
static
int
pmic_glink_rpmsg_callback
(
struct
rpmsg_device
*
rpdev
,
void
*
data
,
int
len
,
void
*
priv
,
u32
addr
)
{
struct
pmic_glink_client
*
client
;
struct
pmic_glink_hdr
*
hdr
;
struct
pmic_glink
*
pg
=
dev_get_drvdata
(
&
rpdev
->
dev
);
if
(
len
<
sizeof
(
*
hdr
))
{
dev_warn
(
pg
->
dev
,
"ignoring truncated message
\n
"
);
return
0
;
}
hdr
=
data
;
list_for_each_entry
(
client
,
&
pg
->
clients
,
node
)
{
if
(
client
->
id
==
le32_to_cpu
(
hdr
->
owner
))
client
->
cb
(
data
,
len
,
client
->
priv
);
}
return
0
;
}
static
void
pmic_glink_aux_release
(
struct
device
*
dev
)
{}
static
int
pmic_glink_add_aux_device
(
struct
pmic_glink
*
pg
,
struct
auxiliary_device
*
aux
,
const
char
*
name
)
{
struct
device
*
parent
=
pg
->
dev
;
int
ret
;
aux
->
name
=
name
;
aux
->
dev
.
parent
=
parent
;
aux
->
dev
.
release
=
pmic_glink_aux_release
;
device_set_of_node_from_dev
(
&
aux
->
dev
,
parent
);
ret
=
auxiliary_device_init
(
aux
);
if
(
ret
)
return
ret
;
ret
=
auxiliary_device_add
(
aux
);
if
(
ret
)
auxiliary_device_uninit
(
aux
);
return
ret
;
}
static
void
pmic_glink_del_aux_device
(
struct
pmic_glink
*
pg
,
struct
auxiliary_device
*
aux
)
{
auxiliary_device_delete
(
aux
);
auxiliary_device_uninit
(
aux
);
}
static
void
pmic_glink_state_notify_clients
(
struct
pmic_glink
*
pg
)
{
struct
pmic_glink_client
*
client
;
unsigned
int
new_state
=
pg
->
client_state
;
if
(
pg
->
client_state
!=
SERVREG_SERVICE_STATE_UP
)
{
if
(
pg
->
pdr_state
==
SERVREG_SERVICE_STATE_UP
&&
pg
->
ept
)
new_state
=
SERVREG_SERVICE_STATE_UP
;
}
else
{
if
(
pg
->
pdr_state
==
SERVREG_SERVICE_STATE_UP
&&
pg
->
ept
)
new_state
=
SERVREG_SERVICE_STATE_DOWN
;
}
if
(
new_state
!=
pg
->
client_state
)
{
list_for_each_entry
(
client
,
&
pg
->
clients
,
node
)
client
->
pdr_notify
(
client
->
priv
,
new_state
);
pg
->
client_state
=
new_state
;
}
}
static
void
pmic_glink_pdr_callback
(
int
state
,
char
*
svc_path
,
void
*
priv
)
{
struct
pmic_glink
*
pg
=
priv
;
mutex_lock
(
&
pg
->
state_lock
);
pg
->
pdr_state
=
state
;
pmic_glink_state_notify_clients
(
pg
);
mutex_unlock
(
&
pg
->
state_lock
);
}
static
int
pmic_glink_rpmsg_probe
(
struct
rpmsg_device
*
rpdev
)
{
struct
pmic_glink
*
pg
=
__pmic_glink
;
int
ret
=
0
;
mutex_lock
(
&
__pmic_glink_lock
);
if
(
!
pg
)
{
ret
=
dev_err_probe
(
&
rpdev
->
dev
,
-
ENODEV
,
"no pmic_glink device to attach to
\n
"
);
goto
out_unlock
;
}
dev_set_drvdata
(
&
rpdev
->
dev
,
pg
);
mutex_lock
(
&
pg
->
state_lock
);
pg
->
ept
=
rpdev
->
ept
;
pmic_glink_state_notify_clients
(
pg
);
mutex_unlock
(
&
pg
->
state_lock
);
out_unlock:
mutex_unlock
(
&
__pmic_glink_lock
);
return
ret
;
}
static
void
pmic_glink_rpmsg_remove
(
struct
rpmsg_device
*
rpdev
)
{
struct
pmic_glink
*
pg
;
mutex_lock
(
&
__pmic_glink_lock
);
pg
=
__pmic_glink
;
if
(
!
pg
)
goto
out_unlock
;
mutex_lock
(
&
pg
->
state_lock
);
pg
->
ept
=
NULL
;
pmic_glink_state_notify_clients
(
pg
);
mutex_unlock
(
&
pg
->
state_lock
);
out_unlock:
mutex_unlock
(
&
__pmic_glink_lock
);
}
static
const
struct
rpmsg_device_id
pmic_glink_rpmsg_id_match
[]
=
{
{
"PMIC_RTR_ADSP_APPS"
},
{}
};
static
struct
rpmsg_driver
pmic_glink_rpmsg_driver
=
{
.
probe
=
pmic_glink_rpmsg_probe
,
.
remove
=
pmic_glink_rpmsg_remove
,
.
callback
=
pmic_glink_rpmsg_callback
,
.
id_table
=
pmic_glink_rpmsg_id_match
,
.
drv
=
{
.
name
=
"qcom_pmic_glink_rpmsg"
,
},
};
static
int
pmic_glink_probe
(
struct
platform_device
*
pdev
)
{
struct
pdr_service
*
service
;
struct
pmic_glink
*
pg
;
int
ret
;
pg
=
devm_kzalloc
(
&
pdev
->
dev
,
sizeof
(
*
pg
),
GFP_KERNEL
);
if
(
!
pg
)
return
-
ENOMEM
;
dev_set_drvdata
(
&
pdev
->
dev
,
pg
);
pg
->
dev
=
&
pdev
->
dev
;
INIT_LIST_HEAD
(
&
pg
->
clients
);
mutex_init
(
&
pg
->
client_lock
);
mutex_init
(
&
pg
->
state_lock
);
ret
=
pmic_glink_add_aux_device
(
pg
,
&
pg
->
altmode_aux
,
"altmode"
);
if
(
ret
)
return
ret
;
ret
=
pmic_glink_add_aux_device
(
pg
,
&
pg
->
ps_aux
,
"power-supply"
);
if
(
ret
)
goto
out_release_altmode_aux
;
pg
->
pdr
=
pdr_handle_alloc
(
pmic_glink_pdr_callback
,
pg
);
if
(
IS_ERR
(
pg
->
pdr
))
{
ret
=
dev_err_probe
(
&
pdev
->
dev
,
PTR_ERR
(
pg
->
pdr
),
"failed to initialize pdr
\n
"
);
goto
out_release_aux_devices
;
}
service
=
pdr_add_lookup
(
pg
->
pdr
,
"tms/servreg"
,
"msm/adsp/charger_pd"
);
if
(
IS_ERR
(
service
))
{
ret
=
dev_err_probe
(
&
pdev
->
dev
,
PTR_ERR
(
service
),
"failed adding pdr lookup for charger_pd
\n
"
);
goto
out_release_pdr_handle
;
}
mutex_lock
(
&
__pmic_glink_lock
);
__pmic_glink
=
pg
;
mutex_unlock
(
&
__pmic_glink_lock
);
return
0
;
out_release_pdr_handle:
pdr_handle_release
(
pg
->
pdr
);
out_release_aux_devices:
pmic_glink_del_aux_device
(
pg
,
&
pg
->
ps_aux
);
out_release_altmode_aux:
pmic_glink_del_aux_device
(
pg
,
&
pg
->
altmode_aux
);
return
ret
;
}
static
int
pmic_glink_remove
(
struct
platform_device
*
pdev
)
{
struct
pmic_glink
*
pg
=
dev_get_drvdata
(
&
pdev
->
dev
);
pdr_handle_release
(
pg
->
pdr
);
pmic_glink_del_aux_device
(
pg
,
&
pg
->
ps_aux
);
pmic_glink_del_aux_device
(
pg
,
&
pg
->
altmode_aux
);
mutex_lock
(
&
__pmic_glink_lock
);
__pmic_glink
=
NULL
;
mutex_unlock
(
&
__pmic_glink_lock
);
return
0
;
}
static
const
struct
of_device_id
pmic_glink_of_match
[]
=
{
{
.
compatible
=
"qcom,pmic-glink"
,
},
{}
};
MODULE_DEVICE_TABLE
(
of
,
pmic_glink_of_match
);
static
struct
platform_driver
pmic_glink_driver
=
{
.
probe
=
pmic_glink_probe
,
.
remove
=
pmic_glink_remove
,
.
driver
=
{
.
name
=
"qcom_pmic_glink"
,
.
of_match_table
=
pmic_glink_of_match
,
},
};
static
int
pmic_glink_init
(
void
)
{
platform_driver_register
(
&
pmic_glink_driver
);
register_rpmsg_driver
(
&
pmic_glink_rpmsg_driver
);
return
0
;
};
module_init
(
pmic_glink_init
);
static
void
pmic_glink_exit
(
void
)
{
unregister_rpmsg_driver
(
&
pmic_glink_rpmsg_driver
);
platform_driver_unregister
(
&
pmic_glink_driver
);
};
module_exit
(
pmic_glink_exit
);
MODULE_DESCRIPTION
(
"Qualcomm PMIC GLINK driver"
);
MODULE_LICENSE
(
"GPL"
);
drivers/soc/qcom/pmic_glink_altmode.c
0 → 100644
View file @
9b4a1915
// 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_device.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/soc/qcom/pdr.h>
#include <drm/drm_bridge.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_mux.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_altmode
dp_alt
;
struct
work_struct
work
;
struct
drm_bridge
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
\n
"
);
}
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
\n
"
);
}
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
)
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
);
if
(
alt_port
->
hpd_state
)
drm_bridge_hpd_notify
(
&
alt_port
->
bridge
,
connector_status_connected
);
else
drm_bridge_hpd_notify
(
&
alt_port
->
bridge
,
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
(
!
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
=
mux
==
2
?
USB_TYPEC_DP_SID
:
0
;
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
(
!
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
int
pmic_glink_altmode_attach
(
struct
drm_bridge
*
bridge
,
enum
drm_bridge_attach_flags
flags
)
{
return
flags
&
DRM_BRIDGE_ATTACH_NO_CONNECTOR
?
0
:
-
EINVAL
;
}
static
const
struct
drm_bridge_funcs
pmic_glink_altmode_bridge_funcs
=
{
.
attach
=
pmic_glink_altmode_attach
,
};
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
\n
"
);
}
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
;
struct
typec_altmode_desc
mux_desc
=
{};
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
);
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
);
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
.
funcs
=
&
pmic_glink_altmode_bridge_funcs
;
alt_port
->
bridge
.
of_node
=
to_of_node
(
fwnode
);
alt_port
->
bridge
.
ops
=
DRM_BRIDGE_OP_HPD
;
alt_port
->
bridge
.
type
=
DRM_MODE_CONNECTOR_USB
;
ret
=
devm_drm_bridge_add
(
dev
,
&
alt_port
->
bridge
);
if
(
ret
)
return
ret
;
alt_port
->
dp_alt
.
svid
=
USB_TYPEC_DP_SID
;
alt_port
->
dp_alt
.
mode
=
USB_TYPEC_DP_MODE
;
alt_port
->
dp_alt
.
active
=
1
;
mux_desc
.
svid
=
USB_TYPEC_DP_SID
;
mux_desc
.
mode
=
USB_TYPEC_DP_MODE
;
alt_port
->
typec_mux
=
fwnode_typec_mux_get
(
fwnode
,
&
mux_desc
);
if
(
IS_ERR
(
alt_port
->
typec_mux
))
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
)
return
ret
;
alt_port
->
typec_switch
=
fwnode_typec_switch_get
(
fwnode
);
if
(
IS_ERR
(
alt_port
->
typec_switch
))
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
)
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"
);
include/linux/soc/qcom/pmic_glink.h
0 → 100644
View file @
9b4a1915
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2022, Linaro Ltd
*/
#ifndef __SOC_QCOM_PMIC_GLINK_H__
#define __SOC_QCOM_PMIC_GLINK_H__
struct
pmic_glink
;
struct
pmic_glink_client
;
#define PMIC_GLINK_OWNER_BATTMGR 32778
#define PMIC_GLINK_OWNER_USBC 32779
#define PMIC_GLINK_OWNER_USBC_PAN 32780
#define PMIC_GLINK_REQ_RESP 1
#define PMIC_GLINK_NOTIFY 2
struct
pmic_glink_hdr
{
__le32
owner
;
__le32
type
;
__le32
opcode
;
};
int
pmic_glink_send
(
struct
pmic_glink_client
*
client
,
void
*
data
,
size_t
len
);
struct
pmic_glink_client
*
devm_pmic_glink_register_client
(
struct
device
*
dev
,
unsigned
int
id
,
void
(
*
cb
)(
const
void
*
,
size_t
,
void
*
),
void
(
*
pdr
)(
void
*
,
int
),
void
*
priv
);
#endif
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment