mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
I3C for 7.4
Subsystem:
- Add APIs for I3C hub support
- Restrict HDR modes to those supported by the bus and target
- Fix ACPI target enumeration on multi-instance controllers by extracting the
bus-controller instance number and PID from the correct _ADR fields.
- allocate IBI workqueue with WQ_HIGHPRI
Drivers:
- dw: Synaptics sl2610 support
- mipi-i3c-hci: HDR-DDR support, cleanup and fixes
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEBqsFVZXh8s/0O5JiY6TcMGxwOjIFAmrhwKAACgkQY6TcMGxw
OjIBtw/+PSorolejkNOUnlT4AgV2CMPohmTAdroM/3RbYyZKKvmocSdn/GTkNDe9
hh9a61NAnYeLu94jJfTh8kfECl/fHM6XNyVahUnhDS/GJZjOhDGe/SDcUyms633O
W6IMIEL+rHuJPziX5t5z1kxtqbLOAaT9L/DmAbWfODyo4hkFu0d0HBFy2Ex/o9VC
VJevHY+FMPbLtrx1ybzdWoVY2b91rKVvc5zonXqvUr3R4VHpN05aOsGbMSsFiZVC
v2sj1RxdDblSa6CnvOqZ8p1/9pY0Ihy6jJ7OD1leBq0R8+R4iKuc7TwdGhwNJJBu
yocAl3bFtgKN+pYdtJUmk/RR1X71fLd62ZaCTI+gzsnx0Yu+TSL1hjX4dUDe0QNi
Ksx4SMiJOqKQywziealuUwgN4ABckPq84dlnPsGQShCN/nylTVqSNbvW+BZeGKYi
uOXB7BqGs+Q9Soyso0uOZzk9EnrGIQ+Huoov6c3G6O4gx2DtOoFm6VnvCX6MY70L
aCl+IvZvkUfGfFoeAjhSKO7dSDYPeGoIp6g74NFRr8+BnSISOhqF/ZTefsDNrRFn
ev6ecrEX7fG2sAOLpaaTfM+GFuAZrBPYNEv0hD6aCBd2s6psD/pM8JST1E/IFnNJ
JH405/+9MXx114o1sn9HQYA9KfCm8VjpYV9jyMVEGbB5rBAKSGc=
=2AuO
-----END PGP SIGNATURE-----
Merge tag 'i3c/for-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull I3C updates from Alexandre Belloni:
"This introduces the I3C hub support in the core with drivers still
being worked on. Then we have the usual fixes and cleanups:
Subsystem:
- Add APIs for I3C hub support
- Restrict HDR modes to those supported by the bus and target
- Fix ACPI target enumeration on multi-instance controllers by
extracting the bus-controller instance number and PID from the
correct _ADR fields.
- allocate IBI workqueue with WQ_HIGHPRI
Drivers:
- dw: Synaptics sl2610 support
- mipi-i3c-hci: HDR-DDR support, cleanup and fixes"
* tag 'i3c/for-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (28 commits)
dt-bindings: i3c: Add Synaptics sl2610 i3c
i3c: dw: switch to array-based exclusive reset control
dt-bindings: i3c: dw: support up to two reset lines
i3c: hub: Add support for the I3C interface in the I3C hub
dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support
i3c: master: Add controller-only device operation helpers
i3c: master: Add APIs for I3C hub support
i3c: master: use named initializers for acpi_device_id
i3c: master: adi: free the IRQ before unregistering the master
i3c: master: amd: Report direct CCC read length in payload.actual_len
i3c: mipi-i3c-hci: Add HDR-DDR support
i3c: Restrict HDR modes to those supported by the bus and target
i3c: mipi-i3c-hci: Support configurable device NACK retries
i3c: mipi-i3c-hci: Remove invalid HDR-BT and Fm/Fm+ definitions
i3c: mipi-i3c-hci: Remove invalid transfer size limit
i3c: mipi-i3c-hci: Correct RESP_DATA_LENGTH to bits 15:0
i3c: mipi-i3c-hci: Stop rings gracefully when suspending
i3c: mipi-i3c-hci: Move DMA suspend/resume callbacks
i3c: mipi-i3c-hci: Process multiple IBIs per interrupt
i3c: mipi-i3c-hci: Fix runtime PM violation in i3c_hci_free_ibi()
...
This commit is contained in:
commit
3ad1536d83
330
Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
Normal file
330
Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
# Copyright 2025-2026 NXP
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/i3c/nxp,p3h2840.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NXP P3H2X4X I3C HUB
|
||||
|
||||
maintainers:
|
||||
- Aman Kumar Pandey <aman.kumarpandey@nxp.com>
|
||||
- Vikash Bansal <vikash.bansal@nxp.com>
|
||||
- Lakshay Piplani <lakshay.piplani@nxp.com>
|
||||
|
||||
description: |
|
||||
P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) is a family of multiport I3C
|
||||
hub devices that connect to:-
|
||||
1. A host CPU via I3C/I2C/SMBus bus on upstream side and connect to multiple
|
||||
peripheral devices on the downstream side.
|
||||
2. Have two Controller Ports which can support either
|
||||
I2C/SMBus or I3C buses and connect to a CPU, BMC or SOC.
|
||||
3. P3H2840/ P3H2841 are 8 port I3C hub with eight Target Ports.
|
||||
4. P3H2440/ P3H2441 are 4 port I3C hub with four Target Ports.
|
||||
Target ports can be configured as SMBus or I3C and connect to
|
||||
peripherals.
|
||||
|
||||
All variants share the same register interface and programmable
|
||||
functionality. The x0 and x1 variants differ only in power-up defaults.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- const: nxp,p3h2840
|
||||
- items:
|
||||
- enum:
|
||||
- nxp,p3h2440
|
||||
- nxp,p3h2441
|
||||
- nxp,p3h2841
|
||||
- const: nxp,p3h2840
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
'#address-cells':
|
||||
const: 1
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
assigned-address:
|
||||
maximum: 0x7f
|
||||
|
||||
nxp,tp0145-pullup-ohms:
|
||||
description:
|
||||
Selects the pull up resistance for target Port 0/1/4/5, in ohms.
|
||||
enum: [250, 500, 1000, 2000]
|
||||
default: 500
|
||||
|
||||
nxp,tp2367-pullup-ohms:
|
||||
description:
|
||||
Selects the pull up resistance for target Port 2/3/6/7, in ohms.
|
||||
enum: [250, 500, 1000, 2000]
|
||||
default: 500
|
||||
|
||||
nxp,cp0-io-strength-ohms:
|
||||
description:
|
||||
Selects the IO drive strength for controller Port 0, in ohms.
|
||||
enum: [20, 30, 40, 50]
|
||||
default: 20
|
||||
|
||||
nxp,cp1-io-strength-ohms:
|
||||
description:
|
||||
Selects the IO drive strength for controller Port 1, in ohms.
|
||||
enum: [20, 30, 40, 50]
|
||||
default: 20
|
||||
|
||||
nxp,tp0145-io-strength-ohms:
|
||||
description:
|
||||
Selects the IO drive strength for target port 0/1/4/5, in ohms.
|
||||
enum: [20, 30, 40, 50]
|
||||
default: 20
|
||||
|
||||
nxp,tp2367-io-strength-ohms:
|
||||
description:
|
||||
Selects the IO drive strength for target port 2/3/6/7, in ohms.
|
||||
enum: [20, 30, 40, 50]
|
||||
default: 20
|
||||
|
||||
vcc1-supply:
|
||||
description: Controller port 0 power supply.
|
||||
|
||||
vcc2-supply:
|
||||
description: Controller port 1 power supply.
|
||||
|
||||
vcc3-supply:
|
||||
description: Target port 0/1/4/5 power supply.
|
||||
|
||||
vcc4-supply:
|
||||
description: Target port 2/3/6/7 power supply.
|
||||
|
||||
regulators:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
||||
properties:
|
||||
ldo-cp0:
|
||||
type: object
|
||||
$ref: /schemas/regulator/regulator.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
ldo-cp1:
|
||||
type: object
|
||||
$ref: /schemas/regulator/regulator.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
ldo-tpg0:
|
||||
type: object
|
||||
$ref: /schemas/regulator/regulator.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
ldo-tpg1:
|
||||
type: object
|
||||
$ref: /schemas/regulator/regulator.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- reg
|
||||
- '#address-cells'
|
||||
- '#size-cells'
|
||||
|
||||
patternProperties:
|
||||
"^i3c@[0-7]$":
|
||||
type: object
|
||||
$ref: /schemas/i3c/i3c.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description:
|
||||
The I3C HUB Target Port number.
|
||||
items:
|
||||
- minimum: 0
|
||||
maximum: 7
|
||||
|
||||
nxp,pullup-enable:
|
||||
type: boolean
|
||||
description:
|
||||
Enables the on-die pull-up for Target Port.
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
"^smbus@[0-7]$":
|
||||
type: object
|
||||
$ref: /schemas/i2c/i2c-controller.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description:
|
||||
The I3C HUB Target Port number.
|
||||
items:
|
||||
- minimum: 0
|
||||
maximum: 7
|
||||
|
||||
nxp,pullup-enable:
|
||||
type: boolean
|
||||
description:
|
||||
Enables the on-die pull-up for Target Port.
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- nxp,p3h2440
|
||||
- nxp,p3h2441
|
||||
then:
|
||||
patternProperties:
|
||||
"^i3c@[0-3]$":
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
- maximum: 3
|
||||
"^smbus@[0-3]$":
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
- maximum: 3
|
||||
"^i3c@[4-7]$": false
|
||||
"^smbus@[4-7]$": false
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i3c {
|
||||
#address-cells = <3>;
|
||||
#size-cells = <0>;
|
||||
|
||||
hub@70,236153000c2 {
|
||||
reg = <0x70 0x236 0x153000c2>;
|
||||
compatible = "nxp,p3h2441", "nxp,p3h2840";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
assigned-address = <0x50>;
|
||||
|
||||
nxp,tp0145-pullup-ohms = <1000>;
|
||||
nxp,tp2367-pullup-ohms = <1000>;
|
||||
nxp,cp0-io-strength-ohms = <50>;
|
||||
nxp,cp1-io-strength-ohms = <50>;
|
||||
nxp,tp0145-io-strength-ohms = <50>;
|
||||
nxp,tp2367-io-strength-ohms = <50>;
|
||||
vcc3-supply = <®_tpg0>;
|
||||
vcc4-supply = <®_tpg1>;
|
||||
|
||||
regulators {
|
||||
reg_cp0: ldo-cp0 {
|
||||
regulator-name = "ldo-cp0";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
|
||||
reg_cp1: ldo-cp1 {
|
||||
regulator-name = "ldo-cp1";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
|
||||
reg_tpg0: ldo-tpg0 {
|
||||
regulator-name = "ldo-tpg0";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
|
||||
reg_tpg1: ldo-tpg1 {
|
||||
regulator-name = "ldo-tpg1";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
};
|
||||
|
||||
smbus@0 {
|
||||
reg = <0x0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
nxp,pullup-enable;
|
||||
};
|
||||
|
||||
smbus@1 {
|
||||
reg = <0x1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
nxp,pullup-enable;
|
||||
};
|
||||
|
||||
i3c@2 {
|
||||
reg = <0x2>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <0>;
|
||||
nxp,pullup-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
hub@70 {
|
||||
reg = <0x70>;
|
||||
compatible = "nxp,p3h2840";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
nxp,tp0145-pullup-ohms = <1000>;
|
||||
nxp,tp2367-pullup-ohms = <1000>;
|
||||
nxp,cp0-io-strength-ohms = <50>;
|
||||
nxp,cp1-io-strength-ohms = <50>;
|
||||
nxp,tp0145-io-strength-ohms = <50>;
|
||||
nxp,tp2367-io-strength-ohms = <50>;
|
||||
vcc3-supply = <®_tpg0_i2c>;
|
||||
vcc4-supply = <®_tpg1_i2c>;
|
||||
|
||||
regulators {
|
||||
reg_cp0_i2c: ldo-cp0 {
|
||||
regulator-name = "ldo-cp0";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
|
||||
reg_cp1_i2c: ldo-cp1 {
|
||||
regulator-name = "ldo-cp1";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
|
||||
reg_tpg0_i2c: ldo-tpg0 {
|
||||
regulator-name = "ldo-tpg0";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
|
||||
reg_tpg1_i2c: ldo-tpg1 {
|
||||
regulator-name = "ldo-tpg1";
|
||||
regulator-min-microvolt = <1800000>;
|
||||
regulator-max-microvolt = <1800000>;
|
||||
};
|
||||
};
|
||||
|
||||
smbus@0 {
|
||||
reg = <0x0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
nxp,pullup-enable;
|
||||
};
|
||||
|
||||
smbus@1 {
|
||||
reg = <0x1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
nxp,pullup-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -12,12 +12,24 @@ maintainers:
|
|||
allOf:
|
||||
- $ref: i3c.yaml#
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: altr,agilex5-dw-i3c-master
|
||||
then:
|
||||
properties:
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- const: snps,dw-i3c-master-1.00a
|
||||
- items:
|
||||
- const: altr,agilex5-dw-i3c-master
|
||||
- enum:
|
||||
- altr,agilex5-dw-i3c-master
|
||||
- syna,sl2610-i3c
|
||||
- const: snps,dw-i3c-master-1.00a
|
||||
|
||||
reg:
|
||||
|
|
@ -39,7 +51,7 @@ properties:
|
|||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
maxItems: 2
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
|
|
|||
|
|
@ -20178,6 +20178,15 @@ L: linux-wireless@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/net/wireless/nxp/
|
||||
|
||||
NXP P3H2X4X I3C-HUB DRIVER
|
||||
M: Vikash Bansal <vikash.bansal@nxp.com>
|
||||
M: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
|
||||
M: Lakshay Piplani <lakshay.piplani@nxp.com>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
L: linux-i3c@lists.infradead.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
|
||||
|
||||
NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
|
||||
M: Woodrow Douglass <wdouglass@carnegierobotics.com>
|
||||
S: Maintained
|
||||
|
|
|
|||
|
|
@ -21,6 +21,20 @@ menuconfig I3C
|
|||
|
||||
if I3C
|
||||
source "drivers/i3c/master/Kconfig"
|
||||
|
||||
config I3C_HUB
|
||||
tristate "I3C Hub Support"
|
||||
help
|
||||
Enable support for the I3C interface in hub devices.
|
||||
|
||||
This option adds virtual I3C bus support for hubs by creating
|
||||
virtual master controllers for downstream ports and forwarding
|
||||
bus operations through the hub device. It also provides an
|
||||
interface used by hub drivers to enable or disable downstream
|
||||
ports during bus transactions.
|
||||
|
||||
Say Y here if your platform includes an I3C hub device
|
||||
|
||||
endif # I3C
|
||||
|
||||
config I3C_OR_I2C
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
i3c-y := device.o master.o
|
||||
obj-$(CONFIG_I3C) += i3c.o
|
||||
obj-$(CONFIG_I3C) += master/
|
||||
obj-$(CONFIG_I3C_HUB) += hub.o
|
||||
|
|
|
|||
|
|
@ -301,16 +301,23 @@ i3c_device_match_id(struct i3c_device *i3cdev,
|
|||
EXPORT_SYMBOL_GPL(i3c_device_match_id);
|
||||
|
||||
/**
|
||||
* i3c_device_get_supported_xfer_mode - Returns the supported transfer mode by
|
||||
* connected master controller.
|
||||
* i3c_device_get_supported_xfer_mode - Returns the transfer modes supported by
|
||||
* the connected master controller and @dev,
|
||||
* and allowed by the bus configuration.
|
||||
* @dev: I3C device
|
||||
*
|
||||
* Return: a bit mask, which supported transfer mode, bit position is defined at
|
||||
* enum i3c_hdr_mode
|
||||
* enum i3c_xfer_mode
|
||||
*/
|
||||
u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev)
|
||||
{
|
||||
return i3c_bus_to_i3c_master(dev->bus)->this->info.hdr_cap | BIT(I3C_SDR);
|
||||
u32 modes;
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
modes = i3c_dev_supported_xfer_modes_locked(dev->desc);
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
|
||||
return modes;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_get_supported_xfer_mode);
|
||||
|
||||
|
|
|
|||
832
drivers/i3c/hub.c
Normal file
832
drivers/i3c/hub.c
Normal file
|
|
@ -0,0 +1,832 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2026 NXP
|
||||
* Generic I3C Hub core implementing virtual controller operations.
|
||||
*/
|
||||
#include <linux/i3c/device.h>
|
||||
#include <linux/i3c/hub.h>
|
||||
#include <linux/lockdep.h>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
/**
|
||||
* struct i3c_hub_dev_data - Per-downstream-device hub state
|
||||
* @parent_desc: Permanent parent-facing descriptor whose master points at the
|
||||
* physical parent controller, used to forward controller-specific
|
||||
* operations there.
|
||||
*
|
||||
* The logical descriptor on the virtual hub bus keeps its master aimed at the
|
||||
* virtual hub controller and is never modified. This separate descriptor lets
|
||||
* the controller-only core helpers resolve the physical parent without racing
|
||||
* concurrent readers on the virtual bus.
|
||||
*/
|
||||
struct i3c_hub_dev_data {
|
||||
struct i3c_dev_desc parent_desc;
|
||||
};
|
||||
|
||||
/*
|
||||
* All i3c_bus rw_semaphores are initialized from a single call site in the
|
||||
* I3C core, so lockdep assigns them one shared class. When a hub forwards an
|
||||
* operation it takes the parent bus lock while already holding its own virtual
|
||||
* bus lock, which lockdep then reports as recursive locking on that shared
|
||||
* class. The bus maintenance and normal-use helpers use plain down_write() and
|
||||
* down_read(), which always acquire with subclass 0, so lockdep_set_subclass()
|
||||
* cannot separate them; a distinct lock_class_key per nesting level is used
|
||||
* instead.
|
||||
*
|
||||
* A top-level hub uses depth 1, a hub behind another hub uses depth 2, and so
|
||||
* on, so a virtual bus lock never shares a class with the parent bus lock it
|
||||
* nests under. Sibling ports on the same hub share a class, which is safe
|
||||
* because they are never nested against each other. The array must stay a
|
||||
* file-local definition: lockdep keys are identified by their address, so a
|
||||
* single set of unique objects is required.
|
||||
*
|
||||
* The depth bound is generous; exceeding it only loses lockdep coverage, not
|
||||
* correctness.
|
||||
*/
|
||||
#define I3C_HUB_MAX_LOCK_DEPTH 8
|
||||
static struct lock_class_key i3c_hub_bus_lock_keys[I3C_HUB_MAX_LOCK_DEPTH];
|
||||
|
||||
/*
|
||||
* The hub routing mutex (hub->lock) serializes port switching and forwarding.
|
||||
* A child hub holds its routing mutex while reaching a parent hub that takes
|
||||
* its own, so it needs the same per-depth lock_class_key treatment as the bus
|
||||
* lock above, keyed identically (top-level hub depth 1, and so on). The class
|
||||
* is assigned once in i3c_hub_init(), not per port, because all ports on a hub
|
||||
* share this single routing mutex.
|
||||
*/
|
||||
static struct lock_class_key i3c_hub_routing_lock_keys[I3C_HUB_MAX_LOCK_DEPTH];
|
||||
|
||||
/**
|
||||
* i3c_hub_controller_depth() - Count hub nesting levels above a controller
|
||||
* @controller: Virtual hub controller being initialized
|
||||
*
|
||||
* Walk the parent chain and count how many stacked hub controllers lead to
|
||||
* @controller. A top-level hub attached to a physical controller returns 1.
|
||||
* The walk stops at the first non-hub (physical) controller.
|
||||
*
|
||||
* Return: The hub nesting depth (>= 1 for a hub controller).
|
||||
*/
|
||||
static unsigned int
|
||||
i3c_hub_controller_depth(struct i3c_master_controller *controller)
|
||||
{
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
unsigned int depth = 0;
|
||||
|
||||
while (controller && controller->ops == &i3c_hub_master_ops) {
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller)
|
||||
break;
|
||||
controller = hub_controller->parent;
|
||||
depth++;
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_master_bus_init() - Bind controller to hub device
|
||||
* @controller: Virtual controller for a hub port
|
||||
*
|
||||
* Associates the virtual controller with the hub device descriptor so that
|
||||
* transfers are executed through the hub on the parent bus.
|
||||
*/
|
||||
static int i3c_hub_master_bus_init(struct i3c_master_controller *controller)
|
||||
{
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_hub *hub;
|
||||
unsigned int depth;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
if (!hub->hub_dev)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Give this virtual bus lock a lockdep class keyed on its hub nesting
|
||||
* depth before the core runs the first DAA (which forwards to the
|
||||
* parent bus and takes the parent lock while this one is held). The
|
||||
* lock is not held here, and controller->ops is already set, so the
|
||||
* class can be assigned safely. Deeper hubs than the key array
|
||||
* supports fall back to the shared class and may warn under lockdep,
|
||||
* but still function correctly.
|
||||
*/
|
||||
depth = i3c_hub_controller_depth(controller);
|
||||
if (depth >= 1 && depth <= I3C_HUB_MAX_LOCK_DEPTH)
|
||||
lockdep_set_class(&controller->bus.lock,
|
||||
&i3c_hub_bus_lock_keys[depth - 1]);
|
||||
else
|
||||
WARN_ONCE(1, "i3c-hub: nesting depth %u exceeds lockdep support\n",
|
||||
depth);
|
||||
|
||||
controller->this = hub->hub_dev->desc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void i3c_hub_master_bus_cleanup(struct i3c_master_controller *controller)
|
||||
{
|
||||
controller->this = NULL;
|
||||
}
|
||||
|
||||
static int i3c_hub_attach_i3c_dev(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *controller = i3c_dev_get_master(dev);
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_hub_dev_data *data;
|
||||
struct i3c_master_controller *parent;
|
||||
struct i3c_hub *hub;
|
||||
int ret;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
if (!hub->hub_dev)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(hub->hub_dev->desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
data = kzalloc_obj(*data);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Initialize the parent-facing descriptor to target the physical parent. */
|
||||
INIT_LIST_HEAD(&data->parent_desc.common.node);
|
||||
mutex_init(&data->parent_desc.ibi_lock);
|
||||
|
||||
data->parent_desc.common.master = parent;
|
||||
data->parent_desc.info = dev->info;
|
||||
|
||||
i3c_bus_maintenance_lock(&parent->bus);
|
||||
ret = i3c_master_attach_i3c_dev_controller_locked(&data->parent_desc);
|
||||
i3c_bus_maintenance_unlock(&parent->bus);
|
||||
if (ret) {
|
||||
mutex_destroy(&data->parent_desc.ibi_lock);
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Link the hub-private data (see struct i3c_hub_dev_data). */
|
||||
i3c_dev_set_master_data(dev, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i3c_hub_reattach_i3c_dev(struct i3c_dev_desc *dev,
|
||||
u8 old_dyn_addr)
|
||||
{
|
||||
struct i3c_hub_dev_data *data = i3c_dev_get_master_data(dev);
|
||||
struct i3c_master_controller *parent;
|
||||
int ret;
|
||||
|
||||
if (!data)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(&data->parent_desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Reattach must not race asynchronous IBI delivery on the parent-facing
|
||||
* descriptor. Once IBI resources are requested the parent controller may
|
||||
* use parent_desc concurrently, so reject a reattach that arrives while
|
||||
* the generic IBI object is still live.
|
||||
*/
|
||||
if (WARN_ON_ONCE(data->parent_desc.ibi))
|
||||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* Re-sync device information after the address change and reattach
|
||||
* under the parent bus lock so both updates are applied as one
|
||||
* operation with respect to parent controller state.
|
||||
*/
|
||||
i3c_bus_maintenance_lock(&parent->bus);
|
||||
data->parent_desc.info = dev->info;
|
||||
ret = i3c_master_reattach_i3c_dev_controller_locked(&data->parent_desc,
|
||||
old_dyn_addr);
|
||||
i3c_bus_maintenance_unlock(&parent->bus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void i3c_hub_detach_i3c_dev(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_hub_dev_data *data = i3c_dev_get_master_data(dev);
|
||||
struct i3c_master_controller *parent;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
parent = i3c_dev_get_master(&data->parent_desc);
|
||||
|
||||
/*
|
||||
* parent_desc.ibi should already be cleared by i3c_hub_free_ibi()
|
||||
* before we get here. If it is still set, the kfree(data) below frees
|
||||
* a descriptor the parent controller can still reach via un-flushed
|
||||
* asynchronous IBI work (use-after-free, not just a leak).
|
||||
*/
|
||||
WARN_ON_ONCE(data->parent_desc.ibi);
|
||||
|
||||
if (parent) {
|
||||
i3c_bus_maintenance_lock(&parent->bus);
|
||||
i3c_master_detach_i3c_dev_controller_locked(&data->parent_desc);
|
||||
i3c_bus_maintenance_unlock(&parent->bus);
|
||||
}
|
||||
|
||||
i3c_dev_set_master_data(dev, NULL);
|
||||
mutex_destroy(&data->parent_desc.ibi_lock);
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_do_daa() - Perform DAA via hub port
|
||||
* @hub: Hub instance
|
||||
* @controller: Virtual controller for a hub port
|
||||
*
|
||||
* Enables the port connection, performs DAA on the parent controller,
|
||||
* then disables the connection.
|
||||
*/
|
||||
static int i3c_hub_do_daa(struct i3c_hub *hub,
|
||||
struct i3c_master_controller *controller)
|
||||
{
|
||||
struct i3c_master_controller *parent;
|
||||
int ret;
|
||||
|
||||
if (!hub || !hub->hub_dev)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(hub->hub_dev->desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Lock order: hub routing mutex before the parent bus lock (taken here
|
||||
* inside i3c_master_do_daa()). The depth-keyed lockdep classes above
|
||||
* keep this nesting acyclic when the parent is itself a hub.
|
||||
*/
|
||||
mutex_lock(&hub->lock);
|
||||
i3c_hub_enable_port(controller);
|
||||
|
||||
/*
|
||||
* Downstream devices reachable through hub target-port routes share the
|
||||
* parent controller's I3C address space. The hub gates access to a
|
||||
* target-port network, but it does not create an independent dynamic
|
||||
* address domain per virtual bus.
|
||||
*
|
||||
* Run DAA on the parent controller so dynamic addresses remain unique
|
||||
* across all downstream devices, even when they are behind different
|
||||
* target ports.
|
||||
*
|
||||
* The current hub model shares the parent controller's dynamic-address
|
||||
* domain and does not support independent per-port DAA domains, so a
|
||||
* DAA result does not go stale behind a port toggle. Addresses reserved
|
||||
* from the Device Tree are held for the lifetime of the parent bus and
|
||||
* are never released on a port toggle, so enabling or disabling a port
|
||||
* only gates reachability and never causes an address to be handed out
|
||||
* twice.
|
||||
*/
|
||||
ret = i3c_master_do_daa(parent);
|
||||
i3c_hub_disable_port(controller);
|
||||
mutex_unlock(&hub->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool i3c_hub_supports_ccc_cmd(struct i3c_hub *hub,
|
||||
const struct i3c_ccc_cmd *cmd)
|
||||
{
|
||||
struct i3c_master_controller *parent;
|
||||
|
||||
if (!hub || !hub->hub_dev)
|
||||
return false;
|
||||
|
||||
parent = i3c_dev_get_master(hub->hub_dev->desc);
|
||||
if (!parent)
|
||||
return false;
|
||||
|
||||
return i3c_master_supports_ccc_cmd(parent, cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_send_ccc_cmd() - Send CCC through hub port
|
||||
* @hub: Hub instance
|
||||
* @controller: Virtual controller
|
||||
* @cmd: CCC command
|
||||
*
|
||||
* Enables the port connection while issuing CCC on the parent controller.
|
||||
*
|
||||
* A hub does not isolate the CCC address space of its virtual buses from the
|
||||
* parent bus. A broadcast CCC issued on one virtual bus is forwarded to the
|
||||
* parent controller and therefore reaches every device on the parent bus, as
|
||||
* well as the downstream devices of any other connected target port. Drivers
|
||||
* that need per-device scope must use the directed form of the command.
|
||||
*/
|
||||
static int i3c_hub_send_ccc_cmd(struct i3c_hub *hub,
|
||||
struct i3c_master_controller *controller,
|
||||
struct i3c_ccc_cmd *cmd)
|
||||
{
|
||||
struct i3c_master_controller *parent;
|
||||
int ret;
|
||||
|
||||
if (!hub || !hub->hub_dev)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(hub->hub_dev->desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&hub->lock);
|
||||
i3c_hub_enable_port(controller);
|
||||
ret = i3c_master_send_ccc_cmd(parent, cmd);
|
||||
i3c_hub_disable_port(controller);
|
||||
mutex_unlock(&hub->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_master_priv_xfers() - Execute private transfers via hub
|
||||
* @dev: Target device descriptor
|
||||
* @xfers: Transfer array
|
||||
* @nxfers: Number of transfers
|
||||
* @mode: Transfer mode (SDR, HDR, etc.)
|
||||
*
|
||||
* Refreshes the parent-facing device info (while no IBI is pending) and
|
||||
* forwards private transfers through the hub to the parent controller.
|
||||
*/
|
||||
static int i3c_hub_master_priv_xfers(struct i3c_dev_desc *dev,
|
||||
struct i3c_xfer *xfers,
|
||||
int nxfers,
|
||||
enum i3c_xfer_mode mode)
|
||||
{
|
||||
struct i3c_master_controller *controller = i3c_dev_get_master(dev);
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_master_controller *parent;
|
||||
struct i3c_hub_dev_data *data;
|
||||
struct i3c_hub *hub;
|
||||
int ret;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
data = i3c_dev_get_master_data(dev);
|
||||
if (!data)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(&data->parent_desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
/* Lock order: hub routing mutex before the parent bus lock (see do_daa). */
|
||||
mutex_lock(&hub->lock);
|
||||
|
||||
/*
|
||||
* Only refresh the parent-facing info while no IBI is requested; once
|
||||
* parent_desc.ibi is set it must stay immutable (see i3c_hub_request_ibi()).
|
||||
*/
|
||||
if (!data->parent_desc.ibi)
|
||||
data->parent_desc.info = dev->info;
|
||||
|
||||
i3c_hub_enable_port(controller);
|
||||
|
||||
i3c_bus_normaluse_lock(&parent->bus);
|
||||
ret = i3c_dev_do_xfers_locked(&data->parent_desc, xfers,
|
||||
nxfers, mode);
|
||||
i3c_bus_normaluse_unlock(&parent->bus);
|
||||
|
||||
i3c_hub_disable_port(controller);
|
||||
|
||||
mutex_unlock(&hub->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i3c_hub_attach_i2c_dev(struct i2c_dev_desc *dev)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static void i3c_hub_detach_i2c_dev(struct i2c_dev_desc *dev)
|
||||
{
|
||||
}
|
||||
|
||||
static int i3c_hub_i2c_xfers(struct i2c_dev_desc *dev,
|
||||
struct i2c_msg *xfers, int nxfers)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int i3c_hub_master_do_daa(struct i3c_master_controller *controller)
|
||||
{
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_hub *hub;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
return i3c_hub_do_daa(hub, controller);
|
||||
}
|
||||
|
||||
static int i3c_hub_master_send_ccc_cmd(struct i3c_master_controller *controller,
|
||||
struct i3c_ccc_cmd *cmd)
|
||||
{
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_hub *hub;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
if (!hub->hub_dev)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Do not forward broadcast RSTDAA through the hub. The hub itself
|
||||
* is visible on the parent bus, so forwarding RSTDAA would also
|
||||
* reset the hub dynamic address. Downstream RSTDAA is not supported
|
||||
* by the hub virtual-controller model.
|
||||
*/
|
||||
if (cmd->id == I3C_CCC_RSTDAA(true))
|
||||
return 0;
|
||||
|
||||
return i3c_hub_send_ccc_cmd(hub, controller, cmd);
|
||||
}
|
||||
|
||||
static bool i3c_hub_master_supports_ccc_cmd(struct i3c_master_controller *controller,
|
||||
const struct i3c_ccc_cmd *cmd)
|
||||
{
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_hub *hub;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return false;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
return i3c_hub_supports_ccc_cmd(hub, cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_request_ibi() - Request IBI through parent controller
|
||||
* @desc: Target device descriptor
|
||||
* @req: IBI setup
|
||||
*
|
||||
* Publishes the generic IBI object on the permanent parent-facing descriptor
|
||||
* and requests IBI for a device connected through the hub. The parent-facing
|
||||
* descriptor references the same IBI object so the physical controller uses
|
||||
* the logical workqueue, pending counter and client device during
|
||||
* asynchronous IBI delivery.
|
||||
*/
|
||||
static int i3c_hub_request_ibi(struct i3c_dev_desc *desc,
|
||||
const struct i3c_ibi_setup *req)
|
||||
{
|
||||
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_master_controller *parent;
|
||||
struct i3c_hub_dev_data *data;
|
||||
struct i3c_hub *hub;
|
||||
int ret;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
data = i3c_dev_get_master_data(desc);
|
||||
if (!data)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(&data->parent_desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Publish the final device information snapshot together with the
|
||||
* generic IBI object under hub->lock. Keep the parent-facing
|
||||
* information immutable while parent_desc.ibi is set and the parent
|
||||
* controller may use the descriptor asynchronously.
|
||||
*/
|
||||
mutex_lock(&hub->lock);
|
||||
data->parent_desc.info = desc->info;
|
||||
data->parent_desc.dev = desc->dev;
|
||||
data->parent_desc.ibi = desc->ibi;
|
||||
mutex_unlock(&hub->lock);
|
||||
|
||||
i3c_bus_normaluse_lock(&parent->bus);
|
||||
ret = i3c_dev_request_ibi_controller_locked(&data->parent_desc, req);
|
||||
i3c_bus_normaluse_unlock(&parent->bus);
|
||||
|
||||
if (ret) {
|
||||
mutex_lock(&hub->lock);
|
||||
data->parent_desc.ibi = NULL;
|
||||
data->parent_desc.dev = NULL;
|
||||
mutex_unlock(&hub->lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void i3c_hub_free_ibi(struct i3c_dev_desc *desc)
|
||||
{
|
||||
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_master_controller *parent;
|
||||
struct i3c_hub_dev_data *data;
|
||||
struct i3c_hub *hub;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
data = i3c_dev_get_master_data(desc);
|
||||
if (!data || !data->parent_desc.ibi)
|
||||
return;
|
||||
|
||||
parent = i3c_dev_get_master(&data->parent_desc);
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
i3c_bus_normaluse_lock(&parent->bus);
|
||||
i3c_dev_free_ibi_controller_locked(&data->parent_desc);
|
||||
i3c_bus_normaluse_unlock(&parent->bus);
|
||||
|
||||
/*
|
||||
* The outer generic IBI free path owns and releases desc->ibi after
|
||||
* this callback returns.
|
||||
*/
|
||||
mutex_lock(&hub->lock);
|
||||
data->parent_desc.ibi = NULL;
|
||||
data->parent_desc.dev = NULL;
|
||||
mutex_unlock(&hub->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_enable_ibi() - Enable IBI via hub port
|
||||
* @desc: Target device descriptor
|
||||
*
|
||||
* Enables port connection and forwards the IBI enable request to the parent
|
||||
* controller.
|
||||
*/
|
||||
static int i3c_hub_enable_ibi(struct i3c_dev_desc *desc)
|
||||
{
|
||||
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_master_controller *parent;
|
||||
struct i3c_hub_dev_data *data;
|
||||
struct i3c_hub *hub;
|
||||
int ret;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
data = i3c_dev_get_master_data(desc);
|
||||
if (!data || !data->parent_desc.ibi)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(&data->parent_desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&hub->lock);
|
||||
|
||||
i3c_hub_enable_port(controller);
|
||||
|
||||
i3c_bus_maintenance_lock(&parent->bus);
|
||||
ret = i3c_dev_enable_ibi_controller_locked(&data->parent_desc);
|
||||
i3c_bus_maintenance_unlock(&parent->bus);
|
||||
|
||||
i3c_hub_disable_port(controller);
|
||||
|
||||
mutex_unlock(&hub->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_disable_ibi() - Disable IBI via hub port
|
||||
* @desc: Target device descriptor
|
||||
*
|
||||
* Enables port connection and forwards the IBI disable request to the parent
|
||||
* controller.
|
||||
*/
|
||||
static int i3c_hub_disable_ibi(struct i3c_dev_desc *desc)
|
||||
{
|
||||
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_master_controller *parent;
|
||||
struct i3c_hub_dev_data *data;
|
||||
struct i3c_hub *hub;
|
||||
int ret;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return -ENODEV;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
data = i3c_dev_get_master_data(desc);
|
||||
if (!data || !data->parent_desc.ibi)
|
||||
return -ENODEV;
|
||||
|
||||
parent = i3c_dev_get_master(&data->parent_desc);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&hub->lock);
|
||||
|
||||
i3c_hub_enable_port(controller);
|
||||
|
||||
i3c_bus_maintenance_lock(&parent->bus);
|
||||
ret = i3c_dev_disable_ibi_controller_locked(&data->parent_desc);
|
||||
i3c_bus_maintenance_unlock(&parent->bus);
|
||||
|
||||
i3c_hub_disable_port(controller);
|
||||
|
||||
mutex_unlock(&hub->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void i3c_hub_recycle_ibi_slot(struct i3c_dev_desc *desc,
|
||||
struct i3c_ibi_slot *slot)
|
||||
{
|
||||
struct i3c_hub_dev_data *data = i3c_dev_get_master_data(desc);
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
i3c_dev_recycle_ibi_slot_controller(&data->parent_desc, slot);
|
||||
}
|
||||
|
||||
const struct i3c_master_controller_ops i3c_hub_master_ops = {
|
||||
.bus_init = i3c_hub_master_bus_init,
|
||||
.bus_cleanup = i3c_hub_master_bus_cleanup,
|
||||
.attach_i3c_dev = i3c_hub_attach_i3c_dev,
|
||||
.reattach_i3c_dev = i3c_hub_reattach_i3c_dev,
|
||||
.detach_i3c_dev = i3c_hub_detach_i3c_dev,
|
||||
.do_daa = i3c_hub_master_do_daa,
|
||||
.supports_ccc_cmd = i3c_hub_master_supports_ccc_cmd,
|
||||
.send_ccc_cmd = i3c_hub_master_send_ccc_cmd,
|
||||
.i3c_xfers = i3c_hub_master_priv_xfers,
|
||||
.attach_i2c_dev = i3c_hub_attach_i2c_dev,
|
||||
.detach_i2c_dev = i3c_hub_detach_i2c_dev,
|
||||
.i2c_xfers = i3c_hub_i2c_xfers,
|
||||
.request_ibi = i3c_hub_request_ibi,
|
||||
.free_ibi = i3c_hub_free_ibi,
|
||||
.enable_ibi = i3c_hub_enable_ibi,
|
||||
.disable_ibi = i3c_hub_disable_ibi,
|
||||
.recycle_ibi_slot = i3c_hub_recycle_ibi_slot,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(i3c_hub_master_ops);
|
||||
|
||||
/**
|
||||
* i3c_hub_set_routing_lock_class() - Key the routing mutex for lockdep
|
||||
* @hub: Hub instance whose routing mutex has just been initialized
|
||||
*
|
||||
* Give @hub->lock a lockdep class keyed on the hub nesting depth, so that a
|
||||
* child hub taking its routing mutex while a parent hub holds its own is not
|
||||
* reported as recursive locking. Does nothing when lockdep is disabled.
|
||||
*/
|
||||
static void i3c_hub_set_routing_lock_class(struct i3c_hub *hub)
|
||||
{
|
||||
struct i3c_master_controller *parent;
|
||||
unsigned int depth;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_LOCKDEP))
|
||||
return;
|
||||
|
||||
/*
|
||||
* The routing mutex has the same hub nesting depth as the virtual
|
||||
* controllers this hub exposes, so the parent controller is one level
|
||||
* shallower. Keying it once here, rather than per port, avoids
|
||||
* reclassifying the single shared routing mutex from a later port that
|
||||
* may already have used it.
|
||||
*/
|
||||
parent = i3c_dev_get_master(hub->hub_dev->desc);
|
||||
depth = i3c_hub_controller_depth(parent) + 1;
|
||||
if (WARN_ONCE(depth > I3C_HUB_MAX_LOCK_DEPTH,
|
||||
"i3c-hub: routing lock depth %u exceeds lockdep support\n",
|
||||
depth))
|
||||
depth = I3C_HUB_MAX_LOCK_DEPTH;
|
||||
|
||||
lockdep_set_class(&hub->lock, &i3c_hub_routing_lock_keys[depth - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_init() - Initialize hub context
|
||||
* @hub: Hub instance to initialize
|
||||
* @ops: Vendor callbacks used to connect and disconnect target ports
|
||||
* @hub_dev: I3C device representing the hub on the parent bus
|
||||
*
|
||||
* Store the vendor callbacks and the hub device in @hub and initialize the
|
||||
* routing mutex used to serialize port switching and forwarding.
|
||||
*
|
||||
* @hub_dev must be a registered I3C device, so it already has a descriptor
|
||||
* and a parent controller.
|
||||
*/
|
||||
void i3c_hub_init(struct i3c_hub *hub,
|
||||
const struct i3c_hub_ops *ops,
|
||||
struct i3c_device *hub_dev)
|
||||
{
|
||||
hub->ops = ops;
|
||||
hub->hub_dev = hub_dev;
|
||||
mutex_init(&hub->lock);
|
||||
|
||||
i3c_hub_set_routing_lock_class(hub);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_hub_init);
|
||||
|
||||
/**
|
||||
* i3c_hub_reserve_parent_addrslots_from_dt() - Reserve child addresses in parent bus.
|
||||
* @hubc: I3C hub controller for a target-port virtual bus.
|
||||
* @node: Target-port bus Device Tree node.
|
||||
*
|
||||
* Reserve parent bus address slots for the assigned dynamic address of each
|
||||
* downstream I3C device described in the Device Tree, so that parent DAA does
|
||||
* not hand the same address to another device. Downstream devices behind hub
|
||||
* target ports share the parent controller's dynamic address space.
|
||||
*
|
||||
* The reservations describe a fixed Device Tree topology and are kept for the
|
||||
* lifetime of the parent bus; they are not released when a target port is
|
||||
* disconnected. If the hub is removed and probed again it re-reads the same
|
||||
* Device Tree and reserves the same addresses, so repeated probing does not
|
||||
* consume additional address space.
|
||||
*
|
||||
* Only the assigned dynamic addresses are reserved here. A hub that supports
|
||||
* hot-joining downstream devices would additionally have to reserve the static
|
||||
* addresses of every device that may appear. Supporting runtime hub hotplug
|
||||
* would also require tracking ownership of these reservations and releasing
|
||||
* them when a hub is removed.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code.
|
||||
*/
|
||||
int i3c_hub_reserve_parent_addrslots_from_dt(struct i3c_hub_controller *hubc,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct i3c_master_controller *parent = hubc->parent;
|
||||
enum i3c_addr_slot_status status;
|
||||
u32 assigned_addr;
|
||||
u32 reg[3];
|
||||
int ret;
|
||||
|
||||
if (!parent || !node)
|
||||
return -ENODEV;
|
||||
|
||||
for_each_available_child_of_node_scoped(node, child) {
|
||||
/*
|
||||
* Only consider addressable bus nodes: a valid "reg" is
|
||||
* required to describe a device, but its static-address value
|
||||
* does not affect the reservation below.
|
||||
*/
|
||||
ret = of_property_read_variable_u32_array(child, "reg", reg, 1, 3);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
ret = of_property_read_u32(child, "assigned-address", &assigned_addr);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
/* Skip nodes without a usable dynamic address. */
|
||||
if (!assigned_addr || assigned_addr > I3C_MAX_ADDR)
|
||||
continue;
|
||||
|
||||
i3c_bus_maintenance_lock(&parent->bus);
|
||||
status = i3c_bus_get_addr_slot_status(&parent->bus,
|
||||
assigned_addr);
|
||||
if (status == I3C_ADDR_SLOT_FREE)
|
||||
i3c_bus_set_addr_slot_status(&parent->bus,
|
||||
assigned_addr,
|
||||
I3C_ADDR_SLOT_I3C_DEV);
|
||||
i3c_bus_maintenance_unlock(&parent->bus);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_hub_reserve_parent_addrslots_from_dt);
|
||||
|
||||
MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>");
|
||||
MODULE_AUTHOR("Vikash Bansal <vikash.bansal@nxp.com>");
|
||||
MODULE_AUTHOR("Lakshay Piplani <lakshay.piplani@nxp.com>");
|
||||
MODULE_DESCRIPTION("Generic I3C hub support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -18,10 +18,26 @@ bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus);
|
|||
void i3c_bus_normaluse_lock(struct i3c_bus *bus);
|
||||
void i3c_bus_normaluse_unlock(struct i3c_bus *bus);
|
||||
|
||||
u32 i3c_dev_supported_xfer_modes_locked(struct i3c_dev_desc *dev);
|
||||
|
||||
int i3c_dev_setdasa_locked(struct i3c_dev_desc *dev);
|
||||
int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev,
|
||||
struct i3c_xfer *xfers,
|
||||
int nxfers, enum i3c_xfer_mode mode);
|
||||
|
||||
int i3c_master_attach_i3c_dev_controller_locked(struct i3c_dev_desc *dev);
|
||||
int i3c_master_reattach_i3c_dev_controller_locked(struct i3c_dev_desc *dev,
|
||||
u8 old_dyn_addr);
|
||||
void i3c_master_detach_i3c_dev_controller_locked(struct i3c_dev_desc *dev);
|
||||
|
||||
int i3c_dev_disable_ibi_controller_locked(struct i3c_dev_desc *dev);
|
||||
int i3c_dev_enable_ibi_controller_locked(struct i3c_dev_desc *dev);
|
||||
int i3c_dev_request_ibi_controller_locked(struct i3c_dev_desc *dev,
|
||||
const struct i3c_ibi_setup *req);
|
||||
void i3c_dev_free_ibi_controller_locked(struct i3c_dev_desc *dev);
|
||||
void i3c_dev_recycle_ibi_slot_controller(struct i3c_dev_desc *dev,
|
||||
struct i3c_ibi_slot *slot);
|
||||
|
||||
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev);
|
||||
int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev);
|
||||
int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <dt-bindings/i3c/i3c.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/delay.h>
|
||||
|
|
@ -49,10 +50,11 @@ static BLOCKING_NOTIFIER_HEAD(i3c_bus_notifier);
|
|||
* logic to rely on I3C device information that could be changed behind their
|
||||
* back.
|
||||
*/
|
||||
static void i3c_bus_maintenance_lock(struct i3c_bus *bus)
|
||||
void i3c_bus_maintenance_lock(struct i3c_bus *bus)
|
||||
{
|
||||
down_write(&bus->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_bus_maintenance_lock);
|
||||
|
||||
/**
|
||||
* i3c_bus_maintenance_unlock - Release the bus lock after a maintenance
|
||||
|
|
@ -63,10 +65,11 @@ static void i3c_bus_maintenance_lock(struct i3c_bus *bus)
|
|||
* i3c_bus_maintenance_lock() for more details on what these maintenance
|
||||
* operations are.
|
||||
*/
|
||||
static void i3c_bus_maintenance_unlock(struct i3c_bus *bus)
|
||||
void i3c_bus_maintenance_unlock(struct i3c_bus *bus)
|
||||
{
|
||||
up_write(&bus->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_bus_maintenance_unlock);
|
||||
|
||||
/**
|
||||
* i3c_bus_normaluse_lock - Lock the bus for a normal operation
|
||||
|
|
@ -88,6 +91,7 @@ void i3c_bus_normaluse_lock(struct i3c_bus *bus)
|
|||
{
|
||||
down_read(&bus->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_bus_normaluse_lock);
|
||||
|
||||
/**
|
||||
* i3c_bus_normaluse_unlock - Release the bus lock after a normal operation
|
||||
|
|
@ -101,6 +105,7 @@ void i3c_bus_normaluse_unlock(struct i3c_bus *bus)
|
|||
{
|
||||
up_read(&bus->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_bus_normaluse_unlock);
|
||||
|
||||
static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev)
|
||||
{
|
||||
|
|
@ -402,11 +407,19 @@ i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask)
|
|||
return status & mask;
|
||||
}
|
||||
|
||||
static enum i3c_addr_slot_status
|
||||
/**
|
||||
* i3c_bus_get_addr_slot_status() - Get I3C bus address slot status
|
||||
* @bus: I3C bus.
|
||||
* @addr: I3C address to query.
|
||||
*
|
||||
* Return: Address slot status for @addr.
|
||||
*/
|
||||
enum i3c_addr_slot_status
|
||||
i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr)
|
||||
{
|
||||
return i3c_bus_get_addr_slot_status_mask(bus, addr, I3C_ADDR_SLOT_STATUS_MASK);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_bus_get_addr_slot_status);
|
||||
|
||||
static void i3c_bus_set_addr_slot_status_mask(struct i3c_bus *bus, u16 addr,
|
||||
enum i3c_addr_slot_status status, u32 mask)
|
||||
|
|
@ -422,11 +435,18 @@ static void i3c_bus_set_addr_slot_status_mask(struct i3c_bus *bus, u16 addr,
|
|||
*ptr |= ((unsigned long)status & mask) << (bitpos % BITS_PER_LONG);
|
||||
}
|
||||
|
||||
static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr,
|
||||
enum i3c_addr_slot_status status)
|
||||
/**
|
||||
* i3c_bus_set_addr_slot_status() - Set I3C bus address slot status
|
||||
* @bus: I3C bus.
|
||||
* @addr: I3C address to update.
|
||||
* @status: Address slot status to set.
|
||||
*/
|
||||
void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr,
|
||||
enum i3c_addr_slot_status status)
|
||||
{
|
||||
i3c_bus_set_addr_slot_status_mask(bus, addr, status, I3C_ADDR_SLOT_STATUS_MASK);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_bus_set_addr_slot_status);
|
||||
|
||||
static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr)
|
||||
{
|
||||
|
|
@ -937,6 +957,61 @@ static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* I3C v1.1.1 Section 5.1.2.4 Table 9 lists the HDR Modes each Bus
|
||||
* Configuration allows. A Mixed Slow / Limited Bus has Legacy I2C Devices
|
||||
* without a 50 ns spike filter, so there is no way to hide any HDR Mode from
|
||||
* them. Of the two Ternary Modes, only HDR-TSL is defined for a Bus that
|
||||
* also has Legacy I2C Devices; HDR-TSP is defined for a Pure Bus.
|
||||
*/
|
||||
static u32 i3c_bus_hdr_modes(struct i3c_bus *bus)
|
||||
{
|
||||
switch (bus->mode) {
|
||||
case I3C_BUS_MODE_PURE:
|
||||
return BIT(I3C_HDR_DDR) | BIT(I3C_HDR_TSP) | BIT(I3C_HDR_TSL);
|
||||
case I3C_BUS_MODE_MIXED_FAST:
|
||||
return BIT(I3C_HDR_DDR) | BIT(I3C_HDR_TSL);
|
||||
case I3C_BUS_MODE_MIXED_LIMITED:
|
||||
case I3C_BUS_MODE_MIXED_SLOW:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 i3c_dev_hdr_modes(struct i3c_dev_desc *dev)
|
||||
{
|
||||
if (!(dev->info.bcr & I3C_BCR_HDR_CAP))
|
||||
return 0;
|
||||
|
||||
return dev->info.hdr_cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_dev_supported_xfer_modes_locked() - Get the transfer modes usable with a
|
||||
* device
|
||||
* @dev: I3C device descriptor
|
||||
*
|
||||
* The HDR Modes the controller and @dev both support, restricted to those the
|
||||
* bus configuration allows. SDR is always supported.
|
||||
*
|
||||
* The bus lock must be held in normal use mode.
|
||||
*
|
||||
* Return: a bit mask of &enum i3c_xfer_mode values.
|
||||
*/
|
||||
u32 i3c_dev_supported_xfer_modes_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
/*
|
||||
* master->this->info.bcr is ignored because it describes the master's
|
||||
* target capability, not its controller capability.
|
||||
*/
|
||||
return (master->this->info.hdr_cap &
|
||||
i3c_bus_hdr_modes(&master->bus) &
|
||||
i3c_dev_hdr_modes(dev)) | BIT(I3C_SDR);
|
||||
}
|
||||
|
||||
static struct i3c_master_controller *
|
||||
i2c_adapter_to_i3c_master(struct i2c_adapter *adap)
|
||||
{
|
||||
|
|
@ -1776,11 +1851,6 @@ static int i3c_master_getstatus_locked(struct i3c_master_controller *master,
|
|||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (dest.payload.len != sizeof(*getstatus)) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status)
|
||||
*status = be16_to_cpu(getstatus->status);
|
||||
out:
|
||||
|
|
@ -1879,6 +1949,30 @@ static int i3c_master_get_i3c_addrs(struct i3c_dev_desc *dev)
|
|||
return -EBUSY;
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_master_attach_i3c_dev_controller_locked() - Attach device state to
|
||||
* controller
|
||||
* @dev: I3C device descriptor
|
||||
*
|
||||
* Invoke the current controller's attach callback without changing address
|
||||
* slot state or adding the device to the controller's device list.
|
||||
*
|
||||
* Context: The caller must hold the bus lock.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code returned by the controller.
|
||||
*/
|
||||
int i3c_master_attach_i3c_dev_controller_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
/* Do not attach the master device itself. */
|
||||
if (master->this != dev && master->ops->attach_i3c_dev)
|
||||
return master->ops->attach_i3c_dev(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_attach_i3c_dev_controller_locked);
|
||||
|
||||
static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
|
||||
struct i3c_dev_desc *dev)
|
||||
{
|
||||
|
|
@ -1896,12 +1990,10 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
|
|||
return ret;
|
||||
|
||||
/* Do not attach the master device itself. */
|
||||
if (master->this != dev && master->ops->attach_i3c_dev) {
|
||||
ret = master->ops->attach_i3c_dev(dev);
|
||||
if (ret) {
|
||||
i3c_master_put_i3c_addrs(dev);
|
||||
return ret;
|
||||
}
|
||||
ret = i3c_master_attach_i3c_dev_controller_locked(dev);
|
||||
if (ret) {
|
||||
i3c_master_put_i3c_addrs(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
list_add_tail(&dev->common.node, &master->bus.devs.i3c);
|
||||
|
|
@ -1909,6 +2001,31 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_master_reattach_i3c_dev_controller_locked() - Reattach controller
|
||||
* device state
|
||||
* @dev: I3C device descriptor
|
||||
* @old_dyn_addr: Previous dynamic address
|
||||
*
|
||||
* Invoke the current controller's reattach callback without modifying the
|
||||
* controller's address-slot state.
|
||||
*
|
||||
* Context: The caller must hold the bus lock.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code returned by the controller.
|
||||
*/
|
||||
int i3c_master_reattach_i3c_dev_controller_locked(struct i3c_dev_desc *dev,
|
||||
u8 old_dyn_addr)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
if (master->ops->reattach_i3c_dev)
|
||||
return master->ops->reattach_i3c_dev(dev, old_dyn_addr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_controller_locked);
|
||||
|
||||
/**
|
||||
* i3c_master_reattach_i3c_dev_locked() - reattach an I3C device with a new address
|
||||
* @dev: I3C device descriptor to reattach
|
||||
|
|
@ -1939,25 +2056,39 @@ int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev,
|
|||
I3C_ADDR_SLOT_FREE);
|
||||
}
|
||||
|
||||
if (master->ops->reattach_i3c_dev) {
|
||||
ret = master->ops->reattach_i3c_dev(dev, old_dyn_addr);
|
||||
if (ret) {
|
||||
i3c_master_put_i3c_addrs(dev);
|
||||
return ret;
|
||||
}
|
||||
ret = i3c_master_reattach_i3c_dev_controller_locked(dev, old_dyn_addr);
|
||||
if (ret) {
|
||||
i3c_master_put_i3c_addrs(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_locked);
|
||||
|
||||
static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
|
||||
/**
|
||||
* i3c_master_detach_i3c_dev_controller_locked() - Detach device state from
|
||||
* controller
|
||||
* @dev: I3C device descriptor
|
||||
*
|
||||
* Invoke the current controller's detach callback without releasing address
|
||||
* slots or removing the device from the controller's device list.
|
||||
*
|
||||
* Context: The caller must hold the bus lock.
|
||||
*/
|
||||
void i3c_master_detach_i3c_dev_controller_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
/* Do not detach the master device itself. */
|
||||
if (master->this != dev && master->ops->detach_i3c_dev)
|
||||
master->ops->detach_i3c_dev(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_detach_i3c_dev_controller_locked);
|
||||
|
||||
static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
|
||||
{
|
||||
i3c_master_detach_i3c_dev_controller_locked(dev);
|
||||
|
||||
i3c_master_put_i3c_addrs(dev);
|
||||
list_del(&dev->common.node);
|
||||
|
|
@ -2216,12 +2347,11 @@ struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *buf,
|
|||
|
||||
if (force_bounce) {
|
||||
dma_xfer->map_len = ALIGN(len, cache_line_size());
|
||||
if (dir == DMA_FROM_DEVICE)
|
||||
bounce = kzalloc(dma_xfer->map_len, GFP_KERNEL);
|
||||
else
|
||||
bounce = kmemdup(buf, dma_xfer->map_len, GFP_KERNEL);
|
||||
bounce = kzalloc(dma_xfer->map_len, GFP_KERNEL);
|
||||
if (!bounce)
|
||||
return NULL;
|
||||
if (dir != DMA_FROM_DEVICE)
|
||||
memcpy(bounce, buf, len);
|
||||
dma_buf = bounce;
|
||||
}
|
||||
|
||||
|
|
@ -2812,6 +2942,59 @@ static void i3c_master_reconcile_dyn_addrs(struct i3c_master_controller *master)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_master_supports_ccc_cmd() - check CCC command support
|
||||
* @master: I3C master controller
|
||||
* @cmd: CCC command to verify
|
||||
*
|
||||
* Return: true if @cmd is supported, false otherwise.
|
||||
*/
|
||||
bool i3c_master_supports_ccc_cmd(struct i3c_master_controller *master,
|
||||
const struct i3c_ccc_cmd *cmd)
|
||||
{
|
||||
if (!master || !cmd)
|
||||
return false;
|
||||
|
||||
if (!master->ops->send_ccc_cmd)
|
||||
return false;
|
||||
|
||||
if (!master->ops->supports_ccc_cmd)
|
||||
return true;
|
||||
|
||||
return master->ops->supports_ccc_cmd(master, cmd);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_supports_ccc_cmd);
|
||||
|
||||
/**
|
||||
* i3c_master_send_ccc_cmd() - send a CCC command
|
||||
* @master: I3C master controller issuing the command
|
||||
* @cmd: CCC command to be sent
|
||||
*
|
||||
* This function sends a Common Command Code (CCC) command to devices on the
|
||||
* I3C bus. It acquires the bus maintenance lock, executes the command, and
|
||||
* then releases the lock to ensure safe access to the bus.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int i3c_master_send_ccc_cmd(struct i3c_master_controller *master,
|
||||
struct i3c_ccc_cmd *cmd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i3c_master_rpm_get(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_maintenance_lock(&master->bus);
|
||||
ret = i3c_master_send_ccc_cmd_locked(master, cmd);
|
||||
i3c_bus_maintenance_unlock(&master->bus);
|
||||
|
||||
i3c_master_rpm_put(master);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_send_ccc_cmd);
|
||||
|
||||
/**
|
||||
* i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version)
|
||||
* @master: controller
|
||||
|
|
@ -3077,6 +3260,13 @@ static int i3c_master_add_of_dev(struct i3c_master_controller *master,
|
|||
return ret;
|
||||
}
|
||||
|
||||
#define I3C_ACPI_ADR_PID GENMASK_U64(47, 0)
|
||||
/*
|
||||
* Zero-based instance number of the Bus Controller to which the Target is
|
||||
* connected.
|
||||
*/
|
||||
#define I3C_ACPI_ADR_INSTANCE GENMASK_U64(51, 48)
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static int i3c_master_add_acpi_dev(struct i3c_master_controller *master,
|
||||
struct fwnode_handle *fwnode)
|
||||
|
|
@ -3084,6 +3274,7 @@ static int i3c_master_add_acpi_dev(struct i3c_master_controller *master,
|
|||
struct acpi_device *adev = to_acpi_device_node(fwnode);
|
||||
acpi_bus_address adr;
|
||||
u32 reg[3] = { 0 };
|
||||
u64 pid;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
|
|
@ -3101,9 +3292,15 @@ static int i3c_master_add_acpi_dev(struct i3c_master_controller *master,
|
|||
|
||||
adr = acpi_device_adr(adev);
|
||||
|
||||
/* Match the multi-bus instance number */
|
||||
if (FIELD_GET(I3C_ACPI_ADR_INSTANCE, adr) != master->instance)
|
||||
return 0;
|
||||
|
||||
/* For I3C devices, _ADR will have the 48 bit PID of the device */
|
||||
reg[1] = upper_32_bits(adr);
|
||||
reg[2] = lower_32_bits(adr);
|
||||
pid = FIELD_GET(I3C_ACPI_ADR_PID, adr);
|
||||
|
||||
reg[1] = upper_32_bits(pid);
|
||||
reg[2] = lower_32_bits(pid);
|
||||
|
||||
fwnode_property_read_u32(fwnode, "mipi-i3c-static-address", ®[0]);
|
||||
|
||||
|
|
@ -3448,7 +3645,6 @@ static void i3c_master_handle_ibi(struct work_struct *work)
|
|||
struct i3c_ibi_slot *slot = container_of(work, struct i3c_ibi_slot,
|
||||
work);
|
||||
struct i3c_dev_desc *dev = slot->dev;
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
struct i3c_ibi_payload payload;
|
||||
|
||||
payload.data = slot->data;
|
||||
|
|
@ -3457,7 +3653,7 @@ static void i3c_master_handle_ibi(struct work_struct *work)
|
|||
if (dev->dev)
|
||||
dev->ibi->handler(dev->dev, &payload);
|
||||
|
||||
master->ops->recycle_ibi_slot(dev, slot);
|
||||
i3c_dev_recycle_ibi_slot_controller(dev, slot);
|
||||
if (atomic_dec_and_test(&dev->ibi->pending_ibis))
|
||||
complete(&dev->ibi->all_ibis_handled);
|
||||
}
|
||||
|
|
@ -3568,6 +3764,29 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_generic_ibi_alloc_pool);
|
||||
|
||||
/**
|
||||
* i3c_dev_recycle_ibi_slot_controller() - Recycle an IBI slot through
|
||||
* the current controller
|
||||
* @dev: I3C device descriptor
|
||||
* @slot: IBI slot to recycle
|
||||
*
|
||||
* Invoke the current controller's IBI slot recycling callback.
|
||||
*
|
||||
* Context: Called from the generic IBI work handler in workqueue context.
|
||||
* No bus lock is taken here: the controller owns its IBI pool and is
|
||||
* responsible for synchronizing access to it. The generic pool
|
||||
* implementation uses its own spinlock.
|
||||
*/
|
||||
void i3c_dev_recycle_ibi_slot_controller(struct i3c_dev_desc *dev,
|
||||
struct i3c_ibi_slot *slot)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
if (master->ops->recycle_ibi_slot)
|
||||
master->ops->recycle_ibi_slot(dev, slot);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_recycle_ibi_slot_controller);
|
||||
|
||||
/**
|
||||
* i3c_generic_ibi_get_free_slot() - Get a free slot from a generic IBI pool
|
||||
* @pool: the pool to query an IBI slot on
|
||||
|
|
@ -3634,30 +3853,23 @@ static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops)
|
|||
}
|
||||
|
||||
/**
|
||||
* i3c_master_register() - register an I3C master
|
||||
* i3c_master_register_fwnode() - register an I3C master with a custom fwnode
|
||||
* @master: master used to send frames on the bus
|
||||
* @parent: the parent device (the one that provides this I3C master
|
||||
* controller)
|
||||
* @parent: the parent device providing this I3C master controller
|
||||
* @fwnode: firmware node describing this I3C bus, or NULL
|
||||
* @ops: the master controller operations
|
||||
* @secondary: true if you are registering a secondary master. Will return
|
||||
* -EOPNOTSUPP if set to true since secondary masters are not yet
|
||||
* supported
|
||||
* @secondary: true if registering a secondary master
|
||||
*
|
||||
* This function takes care of everything for you:
|
||||
*
|
||||
* - creates and initializes the I3C bus
|
||||
* - populates the bus with static I2C devs if @parent->of_node is not
|
||||
* NULL
|
||||
* - registers all I3C devices added by the controller during bus
|
||||
* initialization
|
||||
* - registers the I2C adapter and all I2C devices
|
||||
* This helper is useful for virtual I3C masters whose firmware node is not
|
||||
* the same as @parent's firmware node.
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int i3c_master_register(struct i3c_master_controller *master,
|
||||
struct device *parent,
|
||||
const struct i3c_master_controller_ops *ops,
|
||||
bool secondary)
|
||||
int i3c_master_register_fwnode(struct i3c_master_controller *master,
|
||||
struct device *parent,
|
||||
struct fwnode_handle *fwnode,
|
||||
const struct i3c_master_controller_ops *ops,
|
||||
bool secondary)
|
||||
{
|
||||
unsigned long i2c_scl_rate = I3C_BUS_I2C_FM_PLUS_SCL_MAX_RATE;
|
||||
struct i3c_bus *i3cbus = i3c_master_get_bus(master);
|
||||
|
|
@ -3674,7 +3886,7 @@ int i3c_master_register(struct i3c_master_controller *master,
|
|||
return ret;
|
||||
|
||||
master->dev.parent = parent;
|
||||
device_set_node(&master->dev, fwnode_handle_get(dev_fwnode(parent)));
|
||||
device_set_node(&master->dev, fwnode_handle_get(fwnode));
|
||||
master->dev.bus = &i3c_bus_type;
|
||||
master->dev.type = &i3c_masterdev_type;
|
||||
master->dev.release = i3c_masterdev_release;
|
||||
|
|
@ -3792,6 +4004,38 @@ int i3c_master_register(struct i3c_master_controller *master,
|
|||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_register_fwnode);
|
||||
|
||||
/**
|
||||
* i3c_master_register() - register an I3C master
|
||||
* @master: master used to send frames on the bus
|
||||
* @parent: the parent device (the one that provides this I3C master
|
||||
* controller)
|
||||
* @ops: the master controller operations
|
||||
* @secondary: true if you are registering a secondary master. Will return
|
||||
* -EOPNOTSUPP if set to true since secondary masters are not yet
|
||||
* supported
|
||||
*
|
||||
* This function takes care of everything for you:
|
||||
*
|
||||
* - creates and initializes the I3C bus
|
||||
* - populates the bus with static I2C devs if @parent->of_node is not
|
||||
* NULL
|
||||
* - registers all I3C devices added by the controller during bus
|
||||
* initialization
|
||||
* - registers the I2C adapter and all I2C devices
|
||||
*
|
||||
* Return: 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int i3c_master_register(struct i3c_master_controller *master,
|
||||
struct device *parent,
|
||||
const struct i3c_master_controller_ops *ops,
|
||||
bool secondary)
|
||||
{
|
||||
return i3c_master_register_fwnode(master, parent,
|
||||
dev_fwnode(parent),
|
||||
ops, secondary);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_register);
|
||||
|
||||
/**
|
||||
|
|
@ -3847,11 +4091,38 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
|
|||
if (!master || !xfers)
|
||||
return -EINVAL;
|
||||
|
||||
if (mode != I3C_SDR && !(master->this->info.hdr_cap & BIT(mode)))
|
||||
if (mode != I3C_SDR && !(i3c_dev_supported_xfer_modes_locked(dev) & BIT(mode)))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return master->ops->i3c_xfers(dev, xfers, nxfers, mode);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_do_xfers_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_disable_ibi_controller_locked() - Disable IBI in the controller
|
||||
* @dev: I3C device descriptor
|
||||
*
|
||||
* Invoke the current controller's IBI disable callback without waiting for
|
||||
* pending IBIs or updating the generic IBI enabled state.
|
||||
*
|
||||
* Context: The caller must serialize access to @dev->ibi and the generic
|
||||
* IBI lifecycle.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code.
|
||||
*/
|
||||
int i3c_dev_disable_ibi_controller_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
if (!dev->ibi)
|
||||
return -EINVAL;
|
||||
|
||||
if (!master->ops->disable_ibi)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return master->ops->disable_ibi(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_controller_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_disable_ibi_locked() - Disable IBIs coming from a specific device
|
||||
|
|
@ -3865,14 +4136,9 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
|
|||
*/
|
||||
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master;
|
||||
int ret;
|
||||
|
||||
if (!dev->ibi)
|
||||
return -EINVAL;
|
||||
|
||||
master = i3c_dev_get_master(dev);
|
||||
ret = master->ops->disable_ibi(dev);
|
||||
ret = i3c_dev_disable_ibi_controller_locked(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -3886,6 +4152,32 @@ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_enable_ibi_controller_locked() - Enable controller IBI resources
|
||||
* @dev: I3C device descriptor
|
||||
*
|
||||
* Invoke the current controller's IBI enable callback without updating the
|
||||
* generic IBI enabled state.
|
||||
*
|
||||
* Context: The caller must serialize access to @dev->ibi and the generic
|
||||
* IBI lifecycle.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code.
|
||||
*/
|
||||
int i3c_dev_enable_ibi_controller_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
if (!dev->ibi)
|
||||
return -EINVAL;
|
||||
|
||||
if (!master->ops->enable_ibi)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return master->ops->enable_ibi(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_controller_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_enable_ibi_locked() - Enable IBIs from a specific device (lock held)
|
||||
* @dev: device on which IBIs should be enabled
|
||||
|
|
@ -3902,13 +4194,9 @@ EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked);
|
|||
*/
|
||||
int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
int ret;
|
||||
|
||||
if (!dev->ibi)
|
||||
return -EINVAL;
|
||||
|
||||
ret = master->ops->enable_ibi(dev);
|
||||
ret = i3c_dev_enable_ibi_controller_locked(dev);
|
||||
if (!ret)
|
||||
dev->ibi->enabled = true;
|
||||
|
||||
|
|
@ -3916,6 +4204,35 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_request_ibi_controller_locked() - Request controller IBI resources
|
||||
* @dev: I3C device descriptor
|
||||
* @req: IBI setup request
|
||||
*
|
||||
* Invoke the current controller's IBI request callback without allocating the
|
||||
* generic IBI object or workqueue. The caller must ensure that @dev->ibi has
|
||||
* already been initialized.
|
||||
*
|
||||
* Context: The caller must serialize access to @dev->ibi and the generic
|
||||
* IBI lifecycle.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code.
|
||||
*/
|
||||
int i3c_dev_request_ibi_controller_locked(struct i3c_dev_desc *dev,
|
||||
const struct i3c_ibi_setup *req)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
if (!dev->ibi)
|
||||
return -EINVAL;
|
||||
|
||||
if (!master->ops->request_ibi)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return master->ops->request_ibi(dev, req);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_controller_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_request_ibi_locked() - Request an IBI
|
||||
* @dev: device for which we should enable IBIs
|
||||
|
|
@ -3945,7 +4262,8 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
|
|||
if (!ibi)
|
||||
return -ENOMEM;
|
||||
|
||||
ibi->wq = alloc_ordered_workqueue(dev_name(i3cdev_to_dev(dev->dev)), WQ_MEM_RECLAIM);
|
||||
ibi->wq = alloc_ordered_workqueue(dev_name(i3cdev_to_dev(dev->dev)),
|
||||
WQ_MEM_RECLAIM | WQ_HIGHPRI);
|
||||
if (!ibi->wq) {
|
||||
kfree(ibi);
|
||||
return -ENOMEM;
|
||||
|
|
@ -3958,8 +4276,15 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
|
|||
ibi->num_slots = req->num_slots;
|
||||
|
||||
dev->ibi = ibi;
|
||||
ret = master->ops->request_ibi(dev, req);
|
||||
ret = i3c_dev_request_ibi_controller_locked(dev, req);
|
||||
if (ret) {
|
||||
/*
|
||||
* The controller request callback failed, so tear down the
|
||||
* workqueue allocated above before freeing the IBI object.
|
||||
* This is the owner of the workqueue, so it must destroy it
|
||||
* here to avoid leaking it on the error path.
|
||||
*/
|
||||
destroy_workqueue(ibi->wq);
|
||||
kfree(ibi);
|
||||
dev->ibi = NULL;
|
||||
}
|
||||
|
|
@ -3968,6 +4293,27 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_free_ibi_controller_locked() - Free controller IBI resources
|
||||
* @dev: I3C device descriptor
|
||||
*
|
||||
* Invoke the current controller's IBI free callback without destroying the
|
||||
* generic IBI workqueue or freeing @dev->ibi.
|
||||
*
|
||||
* Context: The caller must serialize access to @dev->ibi and the generic
|
||||
* IBI lifecycle.
|
||||
*/
|
||||
void i3c_dev_free_ibi_controller_locked(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *master = i3c_dev_get_master(dev);
|
||||
|
||||
if (!dev->ibi)
|
||||
return;
|
||||
|
||||
master->ops->free_ibi(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_dev_free_ibi_controller_locked);
|
||||
|
||||
/**
|
||||
* i3c_dev_free_ibi_locked() - Free all resources needed for IBI handling
|
||||
* @dev: device on which you want to release IBI resources
|
||||
|
|
@ -3998,7 +4344,7 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
|
|||
dev_err(&master->dev, "Failed to disable IBI before freeing\n");
|
||||
}
|
||||
|
||||
master->ops->free_ibi(dev);
|
||||
i3c_dev_free_ibi_controller_locked(dev);
|
||||
|
||||
if (dev->ibi->wq) {
|
||||
destroy_workqueue(dev->ibi->wq);
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ struct adi_i3c_master {
|
|||
spinlock_t lock; /* Protect transfer */
|
||||
} xferqueue;
|
||||
void __iomem *regs;
|
||||
int irq;
|
||||
struct clk *clk;
|
||||
unsigned long i3c_scl_lim;
|
||||
struct {
|
||||
|
|
@ -957,6 +958,7 @@ static int adi_i3c_master_probe(struct platform_device *pdev)
|
|||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
master->irq = irq;
|
||||
|
||||
version = readl(master->regs + ADI_AXI_REG_VERSION);
|
||||
if (ADI_AXI_PCORE_VER_MAJOR(version) != 1)
|
||||
|
|
@ -1001,6 +1003,8 @@ static void adi_i3c_master_remove(struct platform_device *pdev)
|
|||
writel(0x00, master->regs + REG_IRQ_MASK);
|
||||
writel(0x01, master->regs + REG_ENABLE);
|
||||
|
||||
devm_free_irq(&pdev->dev, master->irq, master);
|
||||
|
||||
i3c_master_unregister(&master->base);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -806,8 +806,8 @@ static int xi3c_master_send_direct_ccc_cmd(struct xi3c_master *master,
|
|||
|
||||
/* Report actual byte count so the core sees the right length on short reads */
|
||||
if (!ret && ccc->rnw)
|
||||
ccc->dests[0].payload.len = min(xfer->cmds[1].rx_actual,
|
||||
ccc->dests[0].payload.len);
|
||||
ccc->dests[0].payload.actual_len = min(xfer->cmds[1].rx_actual,
|
||||
ccc->dests[0].payload.len);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1657,6 +1657,11 @@ static const struct dw_i3c_platform_ops dw_i3c_platform_ops_default = {
|
|||
.set_dat_ibi = dw_i3c_platform_set_dat_ibi_nop,
|
||||
};
|
||||
|
||||
static void dw_i3c_resets_assert(void *data)
|
||||
{
|
||||
reset_control_assert(data);
|
||||
}
|
||||
|
||||
int dw_i3c_common_probe(struct dw_i3c_master *master,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
|
|
@ -1696,10 +1701,17 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
|
|||
if (IS_ERR(master->pclk))
|
||||
return PTR_ERR(master->pclk);
|
||||
|
||||
master->core_rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev,
|
||||
NULL);
|
||||
if (IS_ERR(master->core_rst))
|
||||
return PTR_ERR(master->core_rst);
|
||||
master->resets = devm_reset_control_array_get_optional_exclusive(&pdev->dev);
|
||||
if (IS_ERR(master->resets))
|
||||
return PTR_ERR(master->resets);
|
||||
|
||||
ret = reset_control_deassert(master->resets);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(&pdev->dev, dw_i3c_resets_assert, master->resets);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spin_lock_init(&master->xferqueue.lock);
|
||||
INIT_LIST_HEAD(&master->xferqueue.list);
|
||||
|
|
@ -1873,7 +1885,7 @@ static int __maybe_unused dw_i3c_master_runtime_suspend(struct device *dev)
|
|||
|
||||
dw_i3c_master_disable(master);
|
||||
|
||||
reset_control_assert(master->core_rst);
|
||||
reset_control_assert(master->resets);
|
||||
dw_i3c_master_disable_clks(master);
|
||||
pinctrl_pm_select_sleep_state(dev);
|
||||
return 0;
|
||||
|
|
@ -1885,7 +1897,7 @@ static int __maybe_unused dw_i3c_master_runtime_resume(struct device *dev)
|
|||
|
||||
pinctrl_pm_select_default_state(dev);
|
||||
dw_i3c_master_enable_clks(master);
|
||||
reset_control_deassert(master->core_rst);
|
||||
reset_control_deassert(master->resets);
|
||||
|
||||
dw_i3c_master_set_intr_regs(master);
|
||||
dw_i3c_master_restore_timing_regs(master);
|
||||
|
|
@ -1934,8 +1946,8 @@ static const struct of_device_id dw_i3c_master_of_match[] = {
|
|||
MODULE_DEVICE_TABLE(of, dw_i3c_master_of_match);
|
||||
|
||||
static const struct acpi_device_id dw_i3c_master_acpi_match[] = {
|
||||
{ "AMDI0015", AMD_I3C_OD_PP_TIMING },
|
||||
{ "NVDA2018", DW_I3C_ACPI_SKIP_CLK_RST },
|
||||
{ .id = "AMDI0015", .driver_data = AMD_I3C_OD_PP_TIMING },
|
||||
{ .id = "NVDA2018", .driver_data = DW_I3C_ACPI_SKIP_CLK_RST },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, dw_i3c_master_acpi_match);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ struct dw_i3c_master {
|
|||
} xferqueue;
|
||||
struct dw_i3c_master_caps caps;
|
||||
void __iomem *regs;
|
||||
struct reset_control *core_rst;
|
||||
struct reset_control *resets;
|
||||
struct clk *core_clk;
|
||||
struct clk *pclk;
|
||||
char version[5];
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
*/
|
||||
#define RESP_STATUS(resp) FIELD_GET(GENMASK(31, 28), resp)
|
||||
#define RESP_TID(resp) FIELD_GET(GENMASK(27, 24), resp)
|
||||
#define RESP_DATA_LENGTH(resp) FIELD_GET(GENMASK(21, 0), resp)
|
||||
#define RESP_DATA_LENGTH(resp) FIELD_GET(GENMASK(15, 0), resp)
|
||||
|
||||
#define RESP_ERR_FIELD GENMASK(31, 28)
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ struct hci_cmd_ops {
|
|||
int (*prep_ccc)(struct i3c_hci *hci, struct hci_xfer *xfer,
|
||||
u8 ccc_addr, u8 ccc_cmd, bool raw);
|
||||
void (*prep_i3c_xfer)(struct i3c_hci *hci, struct i3c_dev_desc *dev,
|
||||
struct hci_xfer *xfer);
|
||||
struct hci_xfer *xfer, enum i3c_xfer_mode mode);
|
||||
void (*prep_i2c_xfer)(struct i3c_hci *hci, struct i2c_dev_desc *dev,
|
||||
struct hci_xfer *xfer);
|
||||
int (*perform_daa)(struct i3c_hci *hci);
|
||||
|
|
|
|||
|
|
@ -108,8 +108,6 @@ enum hci_cmd_mode {
|
|||
MODE_I3C_SDR4 = 0x4,
|
||||
MODE_I3C_HDR_TSx = 0x5,
|
||||
MODE_I3C_HDR_DDR = 0x6,
|
||||
MODE_I3C_HDR_BT = 0x7,
|
||||
MODE_I3C_Fm_FmP = 0x8,
|
||||
MODE_I2C_Fm = 0x0,
|
||||
MODE_I2C_FmP = 0x1,
|
||||
MODE_I2C_UD1 = 0x2,
|
||||
|
|
@ -217,14 +215,30 @@ static int hci_cmd_v1_prep_ccc(struct i3c_hci *hci,
|
|||
|
||||
static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci,
|
||||
struct i3c_dev_desc *dev,
|
||||
struct hci_xfer *xfer)
|
||||
struct hci_xfer *xfer,
|
||||
enum i3c_xfer_mode xfer_mode)
|
||||
{
|
||||
struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev);
|
||||
unsigned int dat_idx = dev_data->dat_idx;
|
||||
enum hci_cmd_mode mode = get_i3c_mode(hci);
|
||||
enum hci_cmd_mode mode;
|
||||
u8 *data = xfer->data;
|
||||
unsigned int data_len = xfer->data_len;
|
||||
bool rnw = xfer->rnw;
|
||||
u32 cp_cmd = 0;
|
||||
|
||||
if (xfer_mode == I3C_SDR) {
|
||||
mode = get_i3c_mode(hci);
|
||||
} else {
|
||||
/*
|
||||
* HDR-DDR is the only advertised HDR Mode, so a non-SDR
|
||||
* transfer uses the HDR-DDR Command Code encoding: CP marks CMD
|
||||
* as valid, CMD carries bits[6:0], and RNW carries bit[7]. CP
|
||||
* and CMD occupy the same descriptor bits in both command
|
||||
* formats.
|
||||
*/
|
||||
mode = MODE_I3C_HDR_DDR;
|
||||
cp_cmd = CMD_R0_CP | CMD_R0_CMD(xfer->hdr_cmd & I3C_HDR_CMD_CODE);
|
||||
}
|
||||
|
||||
xfer->cmd_tid = hci_get_tid();
|
||||
|
||||
|
|
@ -235,7 +249,8 @@ static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci,
|
|||
CMD_I0_TID(xfer->cmd_tid) |
|
||||
CMD_I0_DEV_INDEX(dat_idx) |
|
||||
CMD_I0_DTT(data_len) |
|
||||
CMD_I0_MODE(mode);
|
||||
CMD_I0_MODE(mode) |
|
||||
cp_cmd;
|
||||
fill_data_bytes(xfer, data, data_len);
|
||||
} else {
|
||||
/* we use a Regular Data Transfer Command */
|
||||
|
|
@ -244,7 +259,8 @@ static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci,
|
|||
CMD_R0_TID(xfer->cmd_tid) |
|
||||
CMD_R0_DEV_INDEX(dat_idx) |
|
||||
CMD_R0_MODE(mode) |
|
||||
(rnw ? CMD_R0_RNW : 0);
|
||||
(rnw ? CMD_R0_RNW : 0) |
|
||||
cp_cmd;
|
||||
xfer->cmd_desc[1] =
|
||||
CMD_R1_DATA_LENGTH(data_len);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,9 +212,11 @@ static int hci_cmd_v2_prep_ccc(struct i3c_hci *hci, struct hci_xfer *xfer,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* For cmd_v2, only SDR capability is advertised */
|
||||
static void hci_cmd_v2_prep_i3c_xfer(struct i3c_hci *hci,
|
||||
struct i3c_dev_desc *dev,
|
||||
struct hci_xfer *xfer)
|
||||
struct hci_xfer *xfer,
|
||||
enum i3c_xfer_mode xfer_mode)
|
||||
{
|
||||
unsigned int mode = XFERMODE_IDX_I3C_SDR;
|
||||
unsigned int rate = get_i3c_rate_idx(hci);
|
||||
|
|
|
|||
|
|
@ -51,12 +51,10 @@
|
|||
#define HC_CAP_SG_DC_EN BIT(30)
|
||||
#define HC_CAP_SG_IBI_EN BIT(29)
|
||||
#define HC_CAP_SG_CR_EN BIT(28)
|
||||
#define HC_CAP_MAX_DATA_LENGTH GENMASK(24, 22)
|
||||
#define HC_CAP_CMD_SIZE GENMASK(21, 20)
|
||||
#define HC_CAP_DIRECT_COMMANDS_EN BIT(18)
|
||||
#define HC_CAP_MULTI_LANE_EN BIT(15)
|
||||
#define HC_CAP_CMD_CCC_DEFBYTE BIT(10)
|
||||
#define HC_CAP_HDR_BT_EN BIT(8)
|
||||
#define HC_CAP_HDR_TS_EN BIT(7)
|
||||
#define HC_CAP_HDR_DDR_EN BIT(6)
|
||||
#define HC_CAP_NON_CURRENT_MASTER_CAP BIT(5) /* master handoff capable */
|
||||
|
|
@ -118,6 +116,13 @@ static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m)
|
|||
return container_of(m, struct i3c_hci, master);
|
||||
}
|
||||
|
||||
/* HDR support has been added for cmd_v1 only */
|
||||
static bool i3c_hci_hdr_ddr_capable(struct i3c_hci *hci)
|
||||
{
|
||||
return hci->cmd == &mipi_i3c_hci_cmd_v1 &&
|
||||
hci->caps & HC_CAP_HDR_DDR_EN;
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hci_sysdev() - Get the device to use for DMA and system PM
|
||||
* @dev: Device the HCI controller is bound to
|
||||
|
|
@ -160,6 +165,8 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m)
|
|||
i3c_hci_set_master_dyn_addr(hci);
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.dyn_addr = hci->dyn_addr;
|
||||
if (i3c_hci_hdr_ddr_capable(hci))
|
||||
info.hdr_cap = BIT(I3C_HDR_DDR);
|
||||
ret = i3c_master_set_info(m, &info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -465,6 +472,27 @@ static int i3c_hci_daa(struct i3c_master_controller *m)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool i3c_hci_rnw(struct i3c_xfer *i3c_xfer, enum i3c_xfer_mode mode)
|
||||
{
|
||||
if (mode == I3C_SDR)
|
||||
return i3c_xfer->rnw;
|
||||
|
||||
return i3c_xfer->cmd & I3C_HDR_CMD_RNW;
|
||||
}
|
||||
|
||||
static int i3c_hci_check_hdr_ddr_xfers(struct i3c_xfer *i3c_xfers, int nxfers)
|
||||
{
|
||||
/*
|
||||
* HDR-DDR frames 16-bit Data Words, and at least one Data Word must
|
||||
* follow the Command Word.
|
||||
*/
|
||||
for (int i = 0; i < nxfers; i++)
|
||||
if (i3c_xfers[i].len < 2 || i3c_xfers[i].len % 2)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
|
||||
struct i3c_xfer *i3c_xfers, int nxfers,
|
||||
enum i3c_xfer_mode mode)
|
||||
|
|
@ -473,30 +501,31 @@ static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
|
|||
struct i3c_hci *hci = to_i3c_hci(m);
|
||||
struct hci_xfer *xfer;
|
||||
DECLARE_COMPLETION_ONSTACK(done);
|
||||
unsigned int size_limit;
|
||||
int i, last, ret = 0;
|
||||
|
||||
dev_dbg(&hci->master.dev, "nxfers = %d", nxfers);
|
||||
|
||||
if (mode == I3C_HDR_DDR) {
|
||||
ret = i3c_hci_check_hdr_ddr_xfers(i3c_xfers, nxfers);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
xfer = hci_alloc_xfer(nxfers);
|
||||
if (!xfer)
|
||||
return -ENOMEM;
|
||||
|
||||
size_limit = 1U << (16 + FIELD_GET(HC_CAP_MAX_DATA_LENGTH, hci->caps));
|
||||
|
||||
for (i = 0; i < nxfers; i++) {
|
||||
xfer[i].data_len = i3c_xfers[i].len;
|
||||
ret = -EFBIG;
|
||||
if (xfer[i].data_len >= size_limit)
|
||||
goto out;
|
||||
xfer[i].rnw = i3c_xfers[i].rnw;
|
||||
if (i3c_xfers[i].rnw) {
|
||||
xfer[i].rnw = i3c_hci_rnw(i3c_xfers + i, mode);
|
||||
xfer[i].hdr_cmd = i3c_xfers[i].cmd;
|
||||
if (xfer[i].rnw) {
|
||||
xfer[i].data = i3c_xfers[i].data.in;
|
||||
} else {
|
||||
/* silence the const qualifier warning with a cast */
|
||||
xfer[i].data = (void *) i3c_xfers[i].data.out;
|
||||
}
|
||||
hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i]);
|
||||
hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i], mode);
|
||||
xfer[i].cmd_desc[0] |= CMD_0_ROC;
|
||||
}
|
||||
last = i - 1;
|
||||
|
|
@ -508,7 +537,7 @@ static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
|
|||
if (ret)
|
||||
goto out;
|
||||
for (i = 0; i < nxfers; i++) {
|
||||
if (i3c_xfers[i].rnw)
|
||||
if (xfer[i].rnw)
|
||||
i3c_xfers[i].len = RESP_DATA_LENGTH(xfer[i].response);
|
||||
if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) {
|
||||
ret = -EIO;
|
||||
|
|
@ -563,6 +592,11 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void i3c_hci_dat_v1_set_curr_nack_retry(struct i3c_hci *hci, unsigned int dat_idx)
|
||||
{
|
||||
mipi_i3c_hci_dat_v1.set_nack_retry(hci, dat_idx, hci->master.dev_nack_retry_count);
|
||||
}
|
||||
|
||||
static int i3c_hci_attach_i3c_dev(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
|
|
@ -581,6 +615,7 @@ static int i3c_hci_attach_i3c_dev(struct i3c_dev_desc *dev)
|
|||
}
|
||||
mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, ret,
|
||||
dev->info.dyn_addr ?: dev->info.static_addr);
|
||||
i3c_hci_dat_v1_set_curr_nack_retry(hci, ret);
|
||||
dev_data->dat_idx = ret;
|
||||
}
|
||||
i3c_dev_set_master_data(dev, dev_data);
|
||||
|
|
@ -630,6 +665,7 @@ static int i3c_hci_attach_i2c_dev(struct i2c_dev_desc *dev)
|
|||
}
|
||||
mipi_i3c_hci_dat_v1.set_static_addr(hci, ret, dev->addr);
|
||||
mipi_i3c_hci_dat_v1.set_flags(hci, ret, DAT_0_I2C_DEVICE, 0);
|
||||
i3c_hci_dat_v1_set_curr_nack_retry(hci, ret);
|
||||
dev_data->dat_idx = ret;
|
||||
i2c_dev_set_master_data(dev, dev_data);
|
||||
return 0;
|
||||
|
|
@ -675,11 +711,13 @@ static void __i3c_hci_disable_ibi(struct i3c_hci *hci, struct i3c_dev_desc *dev)
|
|||
|
||||
static void i3c_hci_free_ibi(struct i3c_dev_desc *dev)
|
||||
{
|
||||
struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev);
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct i3c_hci *hci = to_i3c_hci(m);
|
||||
|
||||
/* Must ensure the IBI has been disabled */
|
||||
__i3c_hci_disable_ibi(hci, dev);
|
||||
/* Must ensure IBIs for this device will no longer be processed */
|
||||
scoped_guard(spinlock_irqsave, &hci->lock)
|
||||
hci->ibi_devs[dev_data->dat_idx] = NULL;
|
||||
hci->io->free_ibi(hci, dev);
|
||||
}
|
||||
|
||||
|
|
@ -703,11 +741,15 @@ static int i3c_hci_enable_ibi(struct i3c_dev_desc *dev)
|
|||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct i3c_hci *hci = to_i3c_hci(m);
|
||||
struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev);
|
||||
int ret;
|
||||
|
||||
mipi_i3c_hci_dat_v1.clear_flags(hci, dev_data->dat_idx, DAT_0_SIR_REJECT, 0);
|
||||
scoped_guard(spinlock_irqsave, &hci->lock)
|
||||
hci->ibi_devs[dev_data->dat_idx] = dev;
|
||||
return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
||||
ret = i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
||||
if (ret)
|
||||
__i3c_hci_disable_ibi(hci, dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i3c_hci_disable_ibi(struct i3c_dev_desc *dev)
|
||||
|
|
@ -715,13 +757,14 @@ static int i3c_hci_disable_ibi(struct i3c_dev_desc *dev)
|
|||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct i3c_hci *hci = to_i3c_hci(m);
|
||||
|
||||
__i3c_hci_disable_ibi(hci, dev);
|
||||
/*
|
||||
* The DAT entry is now set to NACK and DISEC this target's IBIs, so
|
||||
* The DAT entry will be set to NACK and DISEC this target's IBIs, so
|
||||
* the IBI teardown can proceed even if DISEC below fails, so ignore
|
||||
* errors.
|
||||
*/
|
||||
i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR);
|
||||
|
||||
__i3c_hci_disable_ibi(hci, dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -734,6 +777,23 @@ static void i3c_hci_recycle_ibi_slot(struct i3c_dev_desc *dev,
|
|||
hci->io->recycle_ibi_slot(hci, dev, slot);
|
||||
}
|
||||
|
||||
static int i3c_hci_set_dev_nack_retry(struct i3c_master_controller *m, unsigned int cnt)
|
||||
{
|
||||
struct i3c_hci *hci = to_i3c_hci(m);
|
||||
unsigned int dat_idx;
|
||||
|
||||
if (hci->cmd != &mipi_i3c_hci_cmd_v1)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (cnt > FIELD_MAX(DAT_0_DEV_NACK_RETRY_CNT))
|
||||
return -ERANGE;
|
||||
|
||||
for_each_set_bit(dat_idx, hci->DAT_data, hci->DAT_entries)
|
||||
mipi_i3c_hci_dat_v1.set_nack_retry(hci, dat_idx, cnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i3c_master_controller_ops i3c_hci_ops = {
|
||||
.bus_init = i3c_hci_bus_init,
|
||||
.bus_cleanup = i3c_hci_bus_cleanup,
|
||||
|
|
@ -753,6 +813,7 @@ static const struct i3c_master_controller_ops i3c_hci_ops = {
|
|||
.recycle_ibi_slot = i3c_hci_recycle_ibi_slot,
|
||||
.enable_hotjoin = i3c_hci_enable_hotjoin,
|
||||
.disable_hotjoin = i3c_hci_disable_hotjoin,
|
||||
.set_dev_nack_retry = i3c_hci_set_dev_nack_retry,
|
||||
};
|
||||
|
||||
static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
|
||||
|
|
@ -1142,8 +1203,10 @@ static int i3c_hci_probe(struct platform_device *pdev)
|
|||
* necessarily in separate contiguous sub-ranges. To avoid overlapping
|
||||
* mappings, provide base_regs from the parent mapping.
|
||||
*/
|
||||
if (pdata)
|
||||
if (pdata) {
|
||||
hci->base_regs = pdata->base_regs;
|
||||
hci->master.instance = pdata->instance;
|
||||
}
|
||||
|
||||
if (!hci->base_regs) {
|
||||
hci->base_regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
|
|
@ -1185,6 +1248,13 @@ static int i3c_hci_probe(struct platform_device *pdev)
|
|||
if (device_can_wakeup(i3c_hci_sysdev(&pdev->dev)))
|
||||
hci->master.ibi_wakeup = true;
|
||||
|
||||
/*
|
||||
* HCI v1.1 onward does 1 retry for Direct CCCs anyway, so for v1.0 to
|
||||
* be consistent, promote 0 to 1.
|
||||
*/
|
||||
if (!hci->master.dev_nack_retry_count)
|
||||
hci->master.dev_nack_retry_count = 1;
|
||||
|
||||
return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false);
|
||||
}
|
||||
|
||||
|
|
@ -1205,8 +1275,10 @@ static const __maybe_unused struct of_device_id i3c_hci_of_match[] = {
|
|||
MODULE_DEVICE_TABLE(of, i3c_hci_of_match);
|
||||
|
||||
static const struct acpi_device_id i3c_hci_acpi_match[] = {
|
||||
{ "AMDI5017", HCI_QUIRK_PIO_MODE | HCI_QUIRK_OD_PP_TIMING | HCI_QUIRK_RESP_BUF_THLD },
|
||||
{}
|
||||
{ .id = "AMDI5017",
|
||||
.driver_data = HCI_QUIRK_PIO_MODE | HCI_QUIRK_OD_PP_TIMING |
|
||||
HCI_QUIRK_RESP_BUF_THLD },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, i3c_hci_acpi_match);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
/* Global DAT flags */
|
||||
#define DAT_0_I2C_DEVICE W0_BIT_(31)
|
||||
#define DAT_0_DEV_NACK_RETRY_CNT W0_MASK(30, 29)
|
||||
#define DAT_0_SIR_REJECT W0_BIT_(13)
|
||||
#define DAT_0_IBI_PAYLOAD W0_BIT_(12)
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ struct hci_dat_ops {
|
|||
void (*clear_flags)(struct i3c_hci *hci, unsigned int dat_idx, u32 w0, u32 w1);
|
||||
int (*get_index)(struct i3c_hci *hci, u8 address);
|
||||
void (*restore)(struct i3c_hci *hci);
|
||||
void (*set_nack_retry)(struct i3c_hci *hci, unsigned int dat_idx, unsigned int cnt);
|
||||
};
|
||||
|
||||
extern const struct hci_dat_ops mipi_i3c_hci_dat_v1;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
#define DAT_1_AUTOCMD_VALUE W1_MASK(47, 40)
|
||||
#define DAT_1_AUTOCMD_MASK W1_MASK(39, 32)
|
||||
/* DAT_0_I2C_DEVICE W0_BIT_(31) */
|
||||
#define DAT_0_DEV_NACK_RETRY_CNT W0_MASK(30, 29)
|
||||
/* DAT_0_DEV_NACK_RETRY_CNT W0_MASK(30, 29) */
|
||||
#define DAT_0_RING_ID W0_MASK(28, 26)
|
||||
#define DAT_0_DYNADDR_PARITY W0_BIT_(23)
|
||||
#define DAT_0_DYNAMIC_ADDRESS W0_MASK(22, 16)
|
||||
|
|
@ -189,6 +189,16 @@ static void hci_dat_v1_restore(struct i3c_hci *hci)
|
|||
}
|
||||
}
|
||||
|
||||
static void hci_dat_v1_set_nack_retry(struct i3c_hci *hci, unsigned int dat_idx, unsigned int cnt)
|
||||
{
|
||||
u32 dat_w0;
|
||||
|
||||
dat_w0 = dat_w0_read(dat_idx);
|
||||
dat_w0 &= ~DAT_0_DEV_NACK_RETRY_CNT;
|
||||
dat_w0 |= FIELD_PREP(DAT_0_DEV_NACK_RETRY_CNT, cnt);
|
||||
dat_w0_write(dat_idx, dat_w0);
|
||||
}
|
||||
|
||||
const struct hci_dat_ops mipi_i3c_hci_dat_v1 = {
|
||||
.init = hci_dat_v1_init,
|
||||
.alloc_entry = hci_dat_v1_alloc_entry,
|
||||
|
|
@ -199,4 +209,5 @@ const struct hci_dat_ops mipi_i3c_hci_dat_v1 = {
|
|||
.clear_flags = hci_dat_v1_clear_flags,
|
||||
.get_index = hci_dat_v1_get_index,
|
||||
.restore = hci_dat_v1_restore,
|
||||
.set_nack_retry = hci_dat_v1_set_nack_retry,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/errno.h>
|
||||
#include <linux/i3c/master.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "hci.h"
|
||||
#include "cmd.h"
|
||||
|
|
@ -273,29 +274,6 @@ static void hci_dma_init_rings(struct i3c_hci *hci)
|
|||
hci_dma_init_rh(hci, &rings->headers[i], i);
|
||||
}
|
||||
|
||||
static void hci_dma_suspend(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
int n = rings ? rings->total : 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
struct hci_rh_data *rh = &rings->headers[i];
|
||||
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, 0);
|
||||
rh_reg_write(RING_CONTROL, 0);
|
||||
}
|
||||
|
||||
i3c_hci_sync_irq_inactive(hci);
|
||||
}
|
||||
|
||||
static void hci_dma_resume(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
|
||||
if (rings)
|
||||
hci_dma_init_rings(hci);
|
||||
}
|
||||
|
||||
static int hci_dma_init(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings;
|
||||
|
|
@ -428,7 +406,7 @@ static void hci_dma_unmap_xfer(struct i3c_hci *hci,
|
|||
static struct i3c_dma *hci_dma_map_xfer(struct device *dev, struct hci_xfer *xfer)
|
||||
{
|
||||
enum dma_data_direction dir = xfer->rnw ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
|
||||
bool need_bounce = device_iommu_mapped(dev) && xfer->rnw && (xfer->data_len & 3);
|
||||
bool need_bounce = xfer->rnw && (xfer->data_len & 3);
|
||||
|
||||
return i3c_master_dma_map_single(dev, xfer->data, xfer->data_len, need_bounce, dir);
|
||||
}
|
||||
|
|
@ -868,25 +846,24 @@ static void hci_dma_recycle_ibi_slot(struct i3c_hci *hci,
|
|||
i3c_generic_ibi_recycle_slot(dev_ibi->pool, slot);
|
||||
}
|
||||
|
||||
static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
|
||||
static bool hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh,
|
||||
u32 *op1_val, unsigned int enq_ptr)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
struct i3c_dev_desc *dev;
|
||||
struct i3c_hci_dev_data *dev_data;
|
||||
struct hci_dma_dev_ibi_data *dev_ibi;
|
||||
struct i3c_ibi_slot *slot;
|
||||
u32 op1_val, op2_val, ibi_status_error;
|
||||
unsigned int ptr, enq_ptr, deq_ptr;
|
||||
u32 ibi_status_error;
|
||||
unsigned int ptr, deq_ptr;
|
||||
unsigned int ibi_size, ibi_chunks, ibi_data_offset, first_part;
|
||||
int ibi_addr, last_ptr;
|
||||
void *ring_ibi_data;
|
||||
dma_addr_t ring_ibi_data_dma;
|
||||
|
||||
op1_val = rh_reg_read(RING_OPERATION1);
|
||||
deq_ptr = FIELD_GET(RING_OP1_IBI_DEQ_PTR, op1_val);
|
||||
|
||||
op2_val = rh_reg_read(RING_OPERATION2);
|
||||
enq_ptr = FIELD_GET(RING_OP2_IBI_ENQ_PTR, op2_val);
|
||||
deq_ptr = FIELD_GET(RING_OP1_IBI_DEQ_PTR, *op1_val);
|
||||
if (deq_ptr == enq_ptr)
|
||||
return false;
|
||||
|
||||
ibi_status_error = 0;
|
||||
ibi_addr = -1;
|
||||
|
|
@ -936,7 +913,7 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
|
|||
dev_dbg(&hci->master.dev,
|
||||
"no LAST_STATUS available (e=%d d=%d)",
|
||||
enq_ptr, deq_ptr);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
deq_ptr = last_ptr + 1;
|
||||
deq_ptr %= rh->ibi_status_entries;
|
||||
|
|
@ -1015,10 +992,9 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
|
|||
i3c_master_queue_ibi(dev, slot);
|
||||
|
||||
done:
|
||||
op1_val = rh_reg_read(RING_OPERATION1);
|
||||
op1_val &= ~RING_OP1_IBI_DEQ_PTR;
|
||||
op1_val |= FIELD_PREP(RING_OP1_IBI_DEQ_PTR, deq_ptr);
|
||||
rh_reg_write(RING_OPERATION1, op1_val);
|
||||
*op1_val &= ~RING_OP1_IBI_DEQ_PTR;
|
||||
*op1_val |= FIELD_PREP(RING_OP1_IBI_DEQ_PTR, deq_ptr);
|
||||
rh_reg_write(RING_OPERATION1, *op1_val);
|
||||
|
||||
/* update the chunk pointer */
|
||||
rh->ibi_chunk_ptr += ibi_chunks;
|
||||
|
|
@ -1026,6 +1002,19 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
|
|||
|
||||
/* and tell the hardware about freed chunks */
|
||||
rh_reg_write(CHUNK_CONTROL, rh_reg_read(CHUNK_CONTROL) + ibi_chunks);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void hci_dma_drain_ibi_ring(struct i3c_hci *hci, struct hci_rh_data *rh)
|
||||
{
|
||||
u32 op1_val = rh_reg_read(RING_OPERATION1);
|
||||
u32 op2_val = rh_reg_read(RING_OPERATION2);
|
||||
unsigned int enq_ptr = FIELD_GET(RING_OP2_IBI_ENQ_PTR, op2_val);
|
||||
|
||||
/* Loop is bounded by enq_ptr. Further IBIs will re-assert INTR_IBI_READY */
|
||||
while (hci_dma_process_ibi(hci, rh, &op1_val, enq_ptr))
|
||||
;
|
||||
}
|
||||
|
||||
static bool hci_dma_irq_handler(struct i3c_hci *hci)
|
||||
|
|
@ -1047,7 +1036,7 @@ static bool hci_dma_irq_handler(struct i3c_hci *hci)
|
|||
rh_reg_write(INTR_STATUS, status);
|
||||
|
||||
if (status & INTR_IBI_READY)
|
||||
hci_dma_process_ibi(hci, rh);
|
||||
hci_dma_drain_ibi_ring(hci, rh);
|
||||
if (status & (INTR_TRANSFER_COMPLETION | INTR_TRANSFER_ERR))
|
||||
hci_dma_xfer_done(hci, rh);
|
||||
if (status & INTR_RING_OP)
|
||||
|
|
@ -1064,6 +1053,77 @@ static bool hci_dma_irq_handler(struct i3c_hci *hci)
|
|||
return handled;
|
||||
}
|
||||
|
||||
/*
|
||||
* With the bus disabled, a ring should stop within a few microseconds. The
|
||||
* timeout is therefore only expected to expire if the hardware is stuck.
|
||||
* Allow sufficient margin for slow systems, but keep the delay acceptable
|
||||
* during suspend.
|
||||
*/
|
||||
#define RING_STOP_TIMEOUT_US (100 * USEC_PER_MSEC)
|
||||
/*
|
||||
* The ring is usually already stopped, so polling typically completes on the
|
||||
* first iteration. Use a modest sleep interval to avoid busy-waiting without
|
||||
* adding excessive latency.
|
||||
*/
|
||||
#define RING_STOP_SLEEP_US 100
|
||||
|
||||
static void hci_dma_suspend(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
int n = rings ? rings->total : 0;
|
||||
struct hci_rh_data *rh;
|
||||
u32 regval;
|
||||
|
||||
/* Gracefully stop the rings */
|
||||
scoped_guard(spinlock_irqsave, &hci->lock) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
rh = &rings->headers[i];
|
||||
regval = rh_reg_read(RING_CONTROL);
|
||||
if (regval & RING_CTRL_RUN_STOP)
|
||||
rh_reg_write(RING_CONTROL, regval & ~RING_CTRL_RUN_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for actual stop */
|
||||
for (int i = 0; i < n; i++) {
|
||||
rh = &rings->headers[i];
|
||||
if (readx_poll_timeout(readl, rh->regs + RH_RING_STATUS, regval,
|
||||
!(regval & RING_STATUS_RUNNING),
|
||||
RING_STOP_SLEEP_US, RING_STOP_TIMEOUT_US))
|
||||
dev_err(&hci->master.dev, "%s: Ring did not stop, status %#x\n",
|
||||
__func__, regval);
|
||||
}
|
||||
|
||||
/*
|
||||
* With the rings stopped, no more IBIs can be received. Flush and make
|
||||
* the interrupt handler inactive.
|
||||
*/
|
||||
i3c_hci_sync_irq_inactive(hci);
|
||||
|
||||
/* Disable interrupt signals and disable the rings */
|
||||
scoped_guard(spinlock_irqsave, &hci->lock)
|
||||
for (int i = 0; i < n; i++) {
|
||||
rh = &rings->headers[i];
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, 0);
|
||||
/*
|
||||
* Be absolutely certain there is no unprocessed IBI.
|
||||
* hci_dma_drain_ibi_ring() will do nothing if there is
|
||||
* none.
|
||||
*/
|
||||
if (i < IBI_RINGS)
|
||||
hci_dma_drain_ibi_ring(hci, rh);
|
||||
rh_reg_write(RING_CONTROL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void hci_dma_resume(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
|
||||
if (rings)
|
||||
hci_dma_init_rings(hci);
|
||||
}
|
||||
|
||||
const struct hci_io_ops mipi_i3c_hci_dma = {
|
||||
.init = hci_dma_init,
|
||||
.cleanup = hci_dma_cleanup,
|
||||
|
|
|
|||
|
|
@ -85,10 +85,10 @@ struct i3c_hci {
|
|||
|
||||
/*
|
||||
* Structure to represent a master initiated transfer.
|
||||
* The rnw, data and data_len fields must be initialized before calling any
|
||||
* hci->cmd->*() method. The cmd method will initialize cmd_desc[] and
|
||||
* possibly modify (clear) the data field. Then xfer->cmd_desc[0] can
|
||||
* be augmented with CMD_0_ROC and/or CMD_0_TOC.
|
||||
* The rnw, data and data_len (and hdr_cmd for HDR) fields must be initialized
|
||||
* before calling any hci->cmd->*() method. The cmd method will initialize
|
||||
* cmd_desc[] and possibly modify (clear) the data field. Then xfer->cmd_desc[0]
|
||||
* can be augmented with CMD_0_ROC and/or CMD_0_TOC.
|
||||
* The completion field needs to be initialized before queueing with
|
||||
* hci->io->queue_xfer(), and requires CMD_0_ROC to be set.
|
||||
*/
|
||||
|
|
@ -97,6 +97,7 @@ struct hci_xfer {
|
|||
u32 response;
|
||||
bool rnw;
|
||||
bool started;
|
||||
u8 hdr_cmd;
|
||||
void *data;
|
||||
unsigned int data_len;
|
||||
unsigned int cmd_tid;
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ static void mipi_i3c_hci_pci_setup_cell(struct mipi_i3c_hci_pci *hci, int idx,
|
|||
struct mfd_cell *cell)
|
||||
{
|
||||
data->pdata.base_regs = hci->base + hci->info->instance_offset[idx];
|
||||
data->pdata.instance = idx;
|
||||
|
||||
data->res = DEFINE_RES_IRQ(0);
|
||||
|
||||
|
|
@ -445,6 +446,8 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pci_set_drvdata(pci, hci);
|
||||
|
||||
hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data;
|
||||
|
||||
ret = hci->info->init ? hci->info->init(hci) : 0;
|
||||
|
|
@ -455,8 +458,6 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci,
|
|||
if (ret)
|
||||
goto err_exit;
|
||||
|
||||
pci_set_drvdata(pci, hci);
|
||||
|
||||
mipi_i3c_hci_pci_rpm_allow(&pci->dev);
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ struct i3c_xfer {
|
|||
enum i3c_error_code err;
|
||||
};
|
||||
|
||||
/* For splitting 'cmd' from struct i3c_xfer */
|
||||
#define I3C_HDR_CMD_RNW BIT(7)
|
||||
#define I3C_HDR_CMD_CODE GENMASK(6, 0)
|
||||
|
||||
/**
|
||||
* enum i3c_dcr - I3C DCR values
|
||||
* @I3C_DCR_GENERIC_DEVICE: generic I3C device
|
||||
|
|
|
|||
92
include/linux/i3c/hub.h
Normal file
92
include/linux/i3c/hub.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright 2026 NXP
|
||||
* Generic hub definitions and helper interfaces.
|
||||
*/
|
||||
#ifndef _LINUX_I3C_HUB_H
|
||||
#define _LINUX_I3C_HUB_H
|
||||
|
||||
#include <linux/i3c/master.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
/**
|
||||
* struct i3c_hub - Generic I3C hub context
|
||||
* @ops: Vendor callbacks for port connection control
|
||||
* @hub_dev: I3C device representing the hub on the parent bus
|
||||
* @lock: Serializes hub port routing/forwarding; its lockdep class is keyed
|
||||
* per hub nesting depth in i3c_hub_init().
|
||||
*/
|
||||
struct i3c_hub {
|
||||
const struct i3c_hub_ops *ops;
|
||||
struct i3c_device *hub_dev;
|
||||
struct mutex lock; /* Serializes hub port routing. */
|
||||
};
|
||||
|
||||
struct i3c_hub_controller {
|
||||
struct i3c_master_controller *parent;
|
||||
struct i3c_master_controller controller;
|
||||
struct i3c_hub *hub;
|
||||
};
|
||||
|
||||
struct i3c_hub_ops {
|
||||
void (*enable_port)(struct i3c_master_controller *controller);
|
||||
void (*disable_port)(struct i3c_master_controller *controller);
|
||||
};
|
||||
|
||||
/**
|
||||
* i3c_hub_enable_port() - Enable hub connection for a controller
|
||||
* @controller: Virtual controller representing a hub port
|
||||
*
|
||||
* Retrieves hub context from controller drvdata and invokes the vendor
|
||||
* callback to enable the associated port connection.
|
||||
*/
|
||||
static inline void i3c_hub_enable_port(struct i3c_master_controller *controller)
|
||||
{
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_hub *hub;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
if (hub && hub->ops && hub->ops->enable_port)
|
||||
hub->ops->enable_port(controller);
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hub_disable_port() - Disable hub connection for a controller
|
||||
* @controller: Virtual controller representing a hub port
|
||||
*
|
||||
* Retrieves hub context from controller drvdata and invokes the vendor
|
||||
* callback to disable the associated port connection.
|
||||
*/
|
||||
static inline void i3c_hub_disable_port(struct i3c_master_controller *controller)
|
||||
{
|
||||
struct i3c_hub_controller *hub_controller;
|
||||
struct i3c_hub *hub;
|
||||
|
||||
hub_controller = dev_get_drvdata(&controller->dev);
|
||||
if (!hub_controller || !hub_controller->hub)
|
||||
return;
|
||||
|
||||
hub = hub_controller->hub;
|
||||
|
||||
if (hub && hub->ops && hub->ops->disable_port)
|
||||
hub->ops->disable_port(controller);
|
||||
}
|
||||
|
||||
/*
|
||||
* Controller operations used by the virtual controllers created for hub
|
||||
* target ports. Hub drivers pass this to i3c_master_register_fwnode().
|
||||
*/
|
||||
extern const struct i3c_master_controller_ops i3c_hub_master_ops;
|
||||
|
||||
void i3c_hub_init(struct i3c_hub *hub,
|
||||
const struct i3c_hub_ops *ops,
|
||||
struct i3c_device *hub_dev);
|
||||
|
||||
int i3c_hub_reserve_parent_addrslots_from_dt(struct i3c_hub_controller *hubc,
|
||||
struct device_node *node);
|
||||
#endif
|
||||
|
|
@ -536,6 +536,8 @@ struct i3c_master_controller_ops {
|
|||
* Bit 0: SETDASA
|
||||
* Bit 1: SETAASA
|
||||
* All other bits are reserved.
|
||||
* @instance: Zero-based instance number of the Bus Controller as defined by the
|
||||
* DisCo specification I3C Target Address (_ADR) Encoding
|
||||
* @wq: freezable workqueue which can be used by master
|
||||
* drivers if they need to postpone operations that need to take place
|
||||
* in a thread context. Typical examples are Hot Join processing which
|
||||
|
|
@ -573,6 +575,7 @@ struct i3c_master_controller {
|
|||
} boardinfo;
|
||||
struct i3c_bus bus;
|
||||
u8 addr_method;
|
||||
u8 instance;
|
||||
struct workqueue_struct *wq;
|
||||
struct work_struct hj_work;
|
||||
struct work_struct reg_work;
|
||||
|
|
@ -649,9 +652,18 @@ DEFINE_FREE(i3c_master_dma_unmap_single, void *,
|
|||
|
||||
int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev,
|
||||
u8 old_dyn_addr);
|
||||
int i3c_master_send_ccc_cmd(struct i3c_master_controller *master,
|
||||
struct i3c_ccc_cmd *cmd);
|
||||
bool i3c_master_supports_ccc_cmd(struct i3c_master_controller *master,
|
||||
const struct i3c_ccc_cmd *cmd);
|
||||
int i3c_master_set_info(struct i3c_master_controller *master,
|
||||
const struct i3c_device_info *info);
|
||||
|
||||
int i3c_master_register_fwnode(struct i3c_master_controller *master,
|
||||
struct device *parent,
|
||||
struct fwnode_handle *fwnode,
|
||||
const struct i3c_master_controller_ops *ops,
|
||||
bool secondary);
|
||||
int i3c_master_register(struct i3c_master_controller *master,
|
||||
struct device *parent,
|
||||
const struct i3c_master_controller_ops *ops,
|
||||
|
|
@ -775,4 +787,12 @@ void i3c_for_each_bus_locked(int (*fn)(struct i3c_bus *bus, void *data),
|
|||
int i3c_register_notifier(struct notifier_block *nb);
|
||||
int i3c_unregister_notifier(struct notifier_block *nb);
|
||||
|
||||
enum i3c_addr_slot_status
|
||||
i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr);
|
||||
|
||||
void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr,
|
||||
enum i3c_addr_slot_status status);
|
||||
|
||||
void i3c_bus_maintenance_lock(struct i3c_bus *bus);
|
||||
void i3c_bus_maintenance_unlock(struct i3c_bus *bus);
|
||||
#endif /* I3C_MASTER_H */
|
||||
|
|
|
|||
|
|
@ -3,13 +3,17 @@
|
|||
#define INCLUDE_PLATFORM_DATA_MIPI_I3C_HCI_H
|
||||
|
||||
#include <linux/compiler_types.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* struct mipi_i3c_hci_platform_data - Platform-dependent data for mipi_i3c_hci
|
||||
* @base_regs: Register set base address (to support multi-bus instances)
|
||||
* @instance: Zero-based instance number of the Bus Controller as defined by the
|
||||
* DisCo specification I3C Target Address (_ADR) Encoding
|
||||
*/
|
||||
struct mipi_i3c_hci_platform_data {
|
||||
void __iomem *base_regs;
|
||||
u8 instance;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user