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:
Linus Torvalds 2026-10-28 07:13:52 -10:00
commit 3ad1536d83
26 changed files with 2024 additions and 156 deletions

View 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 = <&reg_tpg0>;
vcc4-supply = <&reg_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 = <&reg_tpg0_i2c>;
vcc4-supply = <&reg_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;
};
};
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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", &reg[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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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