mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 14:33:24 -04:00
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
This commit is contained in:
@@ -19,6 +19,7 @@ properties:
|
||||
- items:
|
||||
- enum:
|
||||
- axiado,ax3000-i3c
|
||||
- axiado,ax3005-i3c
|
||||
- const: cdns,i3c-master
|
||||
|
||||
reg:
|
||||
|
||||
@@ -31,10 +31,12 @@ properties:
|
||||
described in the device tree, which in turn means we have to describe
|
||||
I3C devices.
|
||||
|
||||
Another use case for describing an I3C device in the device tree is when
|
||||
this I3C device has a static I2C address and we want to assign it a
|
||||
specific I3C dynamic address before the DAA takes place (so that other
|
||||
devices on the bus can't take this dynamic address).
|
||||
Other use-cases for describing an I3C device in the device tree are:
|
||||
- When the I3C device has a static I2C address and we want to assign
|
||||
it a specific I3C dynamic address before the DAA takes place (so
|
||||
that other devices on the bus can't take this dynamic address).
|
||||
- When the I3C device requires SETAASA for its discovery and uses a
|
||||
pre-defined static address.
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
@@ -145,7 +147,31 @@ patternProperties:
|
||||
Dynamic address to be assigned to this device. In case static address is
|
||||
present (first cell of the reg property != 0), this address is assigned
|
||||
through SETDASA. If static address is not present, this address is assigned
|
||||
through SETNEWDA after assigning a temporary address via ENTDAA.
|
||||
through SETNEWDA after assigning a temporary address via ENTDAA. If
|
||||
SETAASA is used, this property is not used, and the static address itself
|
||||
becomes the dynamic address.
|
||||
|
||||
mipi-i3c-static-method:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 0x1
|
||||
maximum: 0x7
|
||||
default: 1
|
||||
description: |
|
||||
Bitmap describing which methods of Dynamic Address Assignment from a
|
||||
static address are supported by this I3C Target. For each defined bit
|
||||
position, a set bit indicates support for that method and a cleared
|
||||
bit indicates lack of support.
|
||||
|
||||
Bit 0: SETDASA CCC (Direct)
|
||||
Bit 1: SETAASA CCC (Broadcast)
|
||||
Bit 2: Other CCC (vendor / standards extension)
|
||||
All other bits are reserved.
|
||||
|
||||
This property follows the MIPI I3C specification. The primary use
|
||||
of this property is to indicate support for SETAASA, i.e Bit 1, but
|
||||
will allow other values mentioned in the specification so that it
|
||||
mirrors the specification. SETDASA will remain as the default method
|
||||
even if this property is not present.
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
@@ -38,6 +38,9 @@ properties:
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
|
||||
58
Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml
Normal file
58
Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/i3c/xlnx,axi-i3c-1.0.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: AMD I3C master
|
||||
|
||||
maintainers:
|
||||
- Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
|
||||
- Shubham Patil <shubhamsanjay.patil@amd.com>
|
||||
|
||||
description:
|
||||
The AXI-I3C IP is an I3C Controller with an AXI4-Lite interface, compatible
|
||||
with the MIPI I3C Specification v1.1.1. The design includes bidirectional I/O
|
||||
buffers that implement open collector drivers for the SDA and SCL signals.
|
||||
External pull-up resistors are required to properly hold the bus at a Logic-1
|
||||
level when the drivers are released.
|
||||
|
||||
For more details, please see https://docs.amd.com/r/en-US/pg439-axi-i3c
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: xlnx,axi-i3c-1.0
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
|
||||
allOf:
|
||||
- $ref: i3c.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
i3c@80000000 {
|
||||
compatible = "xlnx,axi-i3c-1.0";
|
||||
reg = <0x80000000 0x10000>;
|
||||
clocks = <&zynqmp_clk 71>;
|
||||
interrupt-parent = <&imux>;
|
||||
interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
...
|
||||
@@ -1043,6 +1043,14 @@ L: linux-sound@vger.kernel.org
|
||||
S: Supported
|
||||
F: sound/soc/amd/
|
||||
|
||||
AMD AXI I3C MASTER DRIVER
|
||||
M: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
|
||||
M: Shubham Patil <shubhamsanjay.patil@amd.com>
|
||||
L: linux-i3c@lists.infradead.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml
|
||||
F: drivers/i3c/master/amd-i3c-master.c
|
||||
|
||||
AMD AXI W1 DRIVER
|
||||
M: Kris Chaplin <kris.chaplin@amd.com>
|
||||
R: Thomas Delev <thomas.delev@amd.com>
|
||||
|
||||
@@ -2386,12 +2386,15 @@ config SENSORS_INA3221
|
||||
|
||||
config SENSORS_SPD5118
|
||||
tristate "SPD5118 Compliant Temperature Sensors"
|
||||
depends on I2C
|
||||
depends on I3C_OR_I2C
|
||||
select REGMAP_I2C
|
||||
select REGMAP_I3C if I3C
|
||||
help
|
||||
If you say yes here you get support for SPD5118 (JEDEC JESD300)
|
||||
compliant temperature sensors. Such sensors are found on DDR5 memory
|
||||
modules.
|
||||
compliant temperature sensors using I2C or I3C bus interface.
|
||||
Such sensors are found on DDR5 memory modules.
|
||||
|
||||
This driver supports both I2C and I3C interfaces.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called spd5118.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <linux/bits.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i3c/device.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
@@ -66,9 +67,6 @@ static const unsigned short normal_i2c[] = {
|
||||
#define SPD5118_EEPROM_BASE 0x80
|
||||
#define SPD5118_EEPROM_SIZE (SPD5118_PAGE_SIZE * SPD5118_NUM_PAGES)
|
||||
|
||||
#define PAGE_ADDR0(page) (((page) & BIT(0)) << 6)
|
||||
#define PAGE_ADDR1_4(page) (((page) & GENMASK(4, 1)) >> 1)
|
||||
|
||||
/* Temperature unit in millicelsius */
|
||||
#define SPD5118_TEMP_UNIT (MILLIDEGREE_PER_DEGREE / 4)
|
||||
/* Representable temperature range in millicelsius */
|
||||
@@ -78,7 +76,6 @@ static const unsigned short normal_i2c[] = {
|
||||
struct spd5118_data {
|
||||
struct regmap *regmap;
|
||||
struct mutex nvmem_lock;
|
||||
bool is_16bit;
|
||||
};
|
||||
|
||||
/* hwmon */
|
||||
@@ -348,12 +345,7 @@ static ssize_t spd5118_nvmem_read_page(struct spd5118_data *data, char *buf,
|
||||
if (offset + count > SPD5118_PAGE_SIZE)
|
||||
count = SPD5118_PAGE_SIZE - offset;
|
||||
|
||||
if (data->is_16bit) {
|
||||
addr = SPD5118_EEPROM_BASE | PAGE_ADDR0(page) |
|
||||
(PAGE_ADDR1_4(page) << 8);
|
||||
} else {
|
||||
addr = page * 0x100 + SPD5118_EEPROM_BASE;
|
||||
}
|
||||
addr = page * 0x100 + SPD5118_EEPROM_BASE;
|
||||
err = regmap_bulk_read(regmap, addr + offset, buf, count);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -473,10 +465,22 @@ static const struct regmap_config spd5118_regmap8_config = {
|
||||
.num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg),
|
||||
};
|
||||
|
||||
static const struct regmap_config spd5118_regmap16_config = {
|
||||
/*
|
||||
* SPD5118 2-byte register address format (JESD300-5, Tables 7 & 20):
|
||||
* Byte 1 (on wire first): MemReg | BlkAddr[0] | Address[5:0]
|
||||
* Byte 2 (on wire second): 0000 | BlkAddr[4:1]
|
||||
*
|
||||
* The address byte (with MemReg and lower address bits) must be sent first,
|
||||
* followed by the upper block address byte. With regmap 16-bit register
|
||||
* format, this maps to little-endian: the low byte of the 16-bit value is
|
||||
* transmitted first. No range config is needed since I3C does not use MR11
|
||||
* page switching.
|
||||
*/
|
||||
static const struct regmap_config spd5118_regmap_i3c_config = {
|
||||
.reg_bits = 16,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x7ff,
|
||||
.reg_format_endian = REGMAP_ENDIAN_LITTLE,
|
||||
.writeable_reg = spd5118_writeable_reg,
|
||||
.volatile_reg = spd5118_volatile_reg,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
@@ -519,8 +523,7 @@ static int spd5118_resume(struct device *dev)
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume);
|
||||
|
||||
static int spd5118_common_probe(struct device *dev, struct regmap *regmap,
|
||||
bool is_16bit)
|
||||
static int spd5118_common_probe(struct device *dev, struct regmap *regmap)
|
||||
{
|
||||
unsigned int capability, revision, vendor, bank;
|
||||
struct spd5118_data *data;
|
||||
@@ -537,8 +540,6 @@ static int spd5118_common_probe(struct device *dev, struct regmap *regmap,
|
||||
if (!(capability & SPD5118_CAP_TS_SUPPORT))
|
||||
return -ENODEV;
|
||||
|
||||
data->is_16bit = is_16bit;
|
||||
|
||||
err = regmap_read(regmap, SPD5118_REG_REVISION, &revision);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -680,69 +681,21 @@ static int spd5118_i2c_init(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 16-bit addressing note:
|
||||
*
|
||||
* If I2C_FUNC_I2C is not supported by an I2C adapter driver, regmap uses
|
||||
* SMBus operations as alternative. To simulate a read operation with a 16-bit
|
||||
* address, it writes the address using i2c_smbus_write_byte_data(), followed
|
||||
* by one or more calls to i2c_smbus_read_byte() to read the data.
|
||||
* Per spd5118 standard, a read operation after writing the address must start
|
||||
* with <Sr> (Repeat Start). However, a SMBus read byte operation starts with
|
||||
* <S> (Start). This resets the register address in the spd5118 chip. As result,
|
||||
* i2c_smbus_read_byte() always returns data from register address 0x00.
|
||||
*
|
||||
* A working alternative to access chips with 16-bit register addresses in the
|
||||
* absence of I2C_FUNC_I2C support is not known.
|
||||
*
|
||||
* For this reason, 16-bit addressing can only be supported with I2C if the
|
||||
* adapter supports I2C_FUNC_I2C.
|
||||
*
|
||||
* For I2C, the addressing mode selected by the BIOS must not be changed.
|
||||
* Experiments show that at least some PC BIOS versions will not change the
|
||||
* addressing mode on a soft reboot and end up in setup, claiming that some
|
||||
* configuration change happened. This will happen again after a power cycle,
|
||||
* which does reset the addressing mode. To prevent this from happening,
|
||||
* detect if 16-bit addressing is enabled and always use the currently
|
||||
* configured addressing mode.
|
||||
*/
|
||||
|
||||
static int spd5118_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
const struct regmap_config *config;
|
||||
struct device *dev = &client->dev;
|
||||
struct regmap *regmap;
|
||||
int err, mode;
|
||||
bool is_16bit;
|
||||
int err;
|
||||
|
||||
err = spd5118_i2c_init(client);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
|
||||
if (mode < 0)
|
||||
return mode;
|
||||
|
||||
is_16bit = mode & SPD5118_LEGACY_MODE_ADDR;
|
||||
if (is_16bit) {
|
||||
/*
|
||||
* See 16-bit addressing note above explaining why it is
|
||||
* necessary to check for I2C_FUNC_I2C support here.
|
||||
*/
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||
dev_err(dev, "Adapter does not support 16-bit register addresses\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
config = &spd5118_regmap16_config;
|
||||
} else {
|
||||
config = &spd5118_regmap8_config;
|
||||
}
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, config);
|
||||
regmap = devm_regmap_init_i2c(client, &spd5118_regmap8_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n");
|
||||
|
||||
return spd5118_common_probe(dev, regmap, is_16bit);
|
||||
return spd5118_common_probe(dev, regmap);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id spd5118_i2c_id[] = {
|
||||
@@ -770,7 +723,39 @@ static struct i2c_driver spd5118_i2c_driver = {
|
||||
.address_list = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? normal_i2c : NULL,
|
||||
};
|
||||
|
||||
module_i2c_driver(spd5118_i2c_driver);
|
||||
/* I3C */
|
||||
|
||||
static int spd5118_i3c_probe(struct i3c_device *i3cdev)
|
||||
{
|
||||
struct device *dev = i3cdev_to_dev(i3cdev);
|
||||
struct regmap *regmap;
|
||||
u8 regval[2];
|
||||
int err;
|
||||
|
||||
regmap = devm_regmap_init_i3c(i3cdev, &spd5118_regmap_i3c_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n");
|
||||
|
||||
err = regmap_bulk_read(regmap, SPD5118_REG_TYPE, regval, 2);
|
||||
if (err)
|
||||
return dev_err_probe(dev, err, "failed to read device type\n");
|
||||
|
||||
if (regval[0] != 0x51 || regval[1] != 0x18)
|
||||
return -ENODEV;
|
||||
|
||||
return spd5118_common_probe(dev, regmap);
|
||||
}
|
||||
|
||||
static struct i3c_driver spd5118_i3c_driver = {
|
||||
.driver = {
|
||||
.name = "spd5118_i3c",
|
||||
.of_match_table = spd5118_of_ids,
|
||||
.pm = pm_sleep_ptr(&spd5118_pm_ops),
|
||||
},
|
||||
.probe = spd5118_i3c_probe,
|
||||
};
|
||||
|
||||
module_i3c_i2c_driver(spd5118_i3c_driver, &spd5118_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("René Rebe <rene@exactcode.de>");
|
||||
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
|
||||
|
||||
@@ -101,8 +101,7 @@ void i3c_device_get_info(const struct i3c_device *dev,
|
||||
return;
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
if (dev->desc)
|
||||
*info = dev->desc->info;
|
||||
*info = dev->desc->info;
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_get_info);
|
||||
@@ -205,12 +204,14 @@ int i3c_device_request_ibi(struct i3c_device *dev,
|
||||
return ret;
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
if (dev->desc) {
|
||||
if (!dev->desc) {
|
||||
ret = -ENOENT;
|
||||
} else if (!(dev->desc->info.bcr & I3C_BCR_IBI_REQ_CAP)) {
|
||||
ret = -EOPNOTSUPP;
|
||||
} else {
|
||||
mutex_lock(&dev->desc->ibi_lock);
|
||||
ret = i3c_dev_request_ibi_locked(dev->desc, req);
|
||||
mutex_unlock(&dev->desc->ibi_lock);
|
||||
} else {
|
||||
ret = -ENOENT;
|
||||
}
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
|
||||
@@ -309,7 +310,7 @@ EXPORT_SYMBOL_GPL(i3c_device_match_id);
|
||||
*/
|
||||
u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev)
|
||||
{
|
||||
return i3c_dev_get_master(dev->desc)->this->info.hdr_cap | BIT(I3C_SDR);
|
||||
return i3c_bus_to_i3c_master(dev->bus)->this->info.hdr_cap | BIT(I3C_SDR);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_get_supported_xfer_mode);
|
||||
|
||||
|
||||
@@ -72,4 +72,9 @@ static inline void i3c_readl_fifo(const void __iomem *addr, void *buf,
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct i3c_master_controller *i3c_bus_to_i3c_master(struct i3c_bus *i3cbus)
|
||||
{
|
||||
return container_of(i3cbus, struct i3c_master_controller, bus);
|
||||
}
|
||||
|
||||
#endif /* I3C_INTERNAL_H */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -86,3 +86,18 @@ config RENESAS_I3C
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called renesas-i3c.
|
||||
|
||||
config AMD_AXI_I3C_MASTER
|
||||
tristate "AMD AXI I3C Master driver"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Support for the AMD AXI I3C master controller, a soft IP used on
|
||||
AMD (Xilinx) FPGAs and adaptive SoCs with ARM or MicroBlaze
|
||||
processors.
|
||||
|
||||
The controller currently supports Standard Data Rate (SDR) mode.
|
||||
Features include Dynamic Address Assignment, private transfers,
|
||||
and CCC transfers in both broadcast and direct modes.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called amd-i3c-master.
|
||||
|
||||
@@ -6,3 +6,4 @@ obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o
|
||||
obj-$(CONFIG_SVC_I3C_MASTER) += svc-i3c-master.o
|
||||
obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci/
|
||||
obj-$(CONFIG_RENESAS_I3C) += renesas-i3c.o
|
||||
obj-$(CONFIG_AMD_AXI_I3C_MASTER) += amd-i3c-master.o
|
||||
|
||||
@@ -246,6 +246,7 @@ static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
|
||||
if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
|
||||
rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
|
||||
adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
|
||||
cmd->rx_len = rx_len;
|
||||
}
|
||||
cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
|
||||
}
|
||||
@@ -360,6 +361,8 @@ static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
adi_i3c_master_unqueue_xfer(master, xfer);
|
||||
|
||||
cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
|
||||
if (!xfer->ret && cmd->rnw)
|
||||
cmd->dests[0].payload.actual_len = ccmd->rx_len;
|
||||
|
||||
return xfer->ret;
|
||||
}
|
||||
@@ -929,6 +932,7 @@ static const struct of_device_id adi_i3c_master_of_match[] = {
|
||||
{ .compatible = "adi,i3c-master-v1" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adi_i3c_master_of_match);
|
||||
|
||||
static int adi_i3c_master_probe(struct platform_device *pdev)
|
||||
{
|
||||
@@ -964,17 +968,9 @@ static int adi_i3c_master_probe(struct platform_device *pdev)
|
||||
writel(0x00, master->regs + REG_ENABLE);
|
||||
writel(0x00, master->regs + REG_IRQ_MASK);
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
|
||||
dev_name(&pdev->dev), master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, master);
|
||||
|
||||
master->free_rr_slots = GENMASK(ADI_MAX_DEVS, 1);
|
||||
|
||||
writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
|
||||
|
||||
spin_lock_init(&master->ibi.lock);
|
||||
master->ibi.num_slots = 15;
|
||||
master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
|
||||
@@ -986,6 +982,13 @@ static int adi_i3c_master_probe(struct platform_device *pdev)
|
||||
spin_lock_init(&master->xferqueue.lock);
|
||||
INIT_LIST_HEAD(&master->xferqueue.list);
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
|
||||
dev_name(&pdev->dev), master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
|
||||
|
||||
return i3c_master_register(&master->base, &pdev->dev,
|
||||
&adi_i3c_master_ops, false);
|
||||
}
|
||||
|
||||
1124
drivers/i3c/master/amd-i3c-master.c
Normal file
1124
drivers/i3c/master/amd-i3c-master.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -242,15 +242,16 @@
|
||||
/* List of quirks */
|
||||
#define AMD_I3C_OD_PP_TIMING BIT(1)
|
||||
#define DW_I3C_DISABLE_RUNTIME_PM_QUIRK BIT(2)
|
||||
#define DW_I3C_ACPI_SKIP_CLK_RST BIT(3)
|
||||
|
||||
struct dw_i3c_cmd {
|
||||
u32 cmd_lo;
|
||||
u32 cmd_hi;
|
||||
u16 tx_len;
|
||||
const void *tx_buf;
|
||||
u16 rx_len;
|
||||
void *rx_buf;
|
||||
u8 error;
|
||||
const void *tx_buf;
|
||||
void *rx_buf;
|
||||
};
|
||||
|
||||
struct dw_i3c_xfer {
|
||||
@@ -309,6 +310,8 @@ static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
|
||||
case I3C_CCC_GETSTATUS:
|
||||
case I3C_CCC_GETMXDS:
|
||||
case I3C_CCC_GETHDRCAP:
|
||||
case I3C_CCC_SETAASA:
|
||||
case I3C_CCC_VENDOR(0, true): /* SETHID */
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -493,6 +496,7 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
|
||||
break;
|
||||
case RESPONSE_ERROR_PARITY:
|
||||
case RESPONSE_ERROR_IBA_NACK:
|
||||
case RESPONSE_ERROR_ADDRESS_NACK:
|
||||
case RESPONSE_ERROR_TRANSF_ABORT:
|
||||
case RESPONSE_ERROR_CRC:
|
||||
case RESPONSE_ERROR_FRAME:
|
||||
@@ -502,7 +506,6 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
|
||||
ret = -ENOSPC;
|
||||
break;
|
||||
case RESPONSE_ERROR_I2C_W_NACK_ERR:
|
||||
case RESPONSE_ERROR_ADDRESS_NACK:
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
@@ -554,13 +557,35 @@ static void dw_i3c_master_set_intr_regs(struct dw_i3c_master *master)
|
||||
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
|
||||
}
|
||||
|
||||
static unsigned long dw_i3c_master_get_core_rate(struct dw_i3c_master *master)
|
||||
{
|
||||
unsigned int core_rate_prop;
|
||||
|
||||
if (master->core_clk)
|
||||
return clk_get_rate(master->core_clk);
|
||||
|
||||
if (device_property_read_u32(master->dev, "clock-frequency", &core_rate_prop)) {
|
||||
dev_err(master->dev, "missing clock-frequency property\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return core_rate_prop;
|
||||
}
|
||||
|
||||
static void amd_configure_od_pp_quirk(struct dw_i3c_master *master)
|
||||
{
|
||||
master->i3c_od_timing = AMD_I3C_OD_TIMING;
|
||||
master->i3c_od_timing_normal = AMD_I3C_OD_TIMING;
|
||||
master->i3c_pp_timing = AMD_I3C_PP_TIMING;
|
||||
}
|
||||
|
||||
static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
|
||||
{
|
||||
unsigned long core_rate, core_period;
|
||||
u32 scl_timing;
|
||||
u8 hcnt, lcnt;
|
||||
|
||||
core_rate = clk_get_rate(master->core_clk);
|
||||
core_rate = dw_i3c_master_get_core_rate(master);
|
||||
if (!core_rate)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -592,6 +617,18 @@ static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
|
||||
scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
|
||||
writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
|
||||
master->i3c_od_timing = scl_timing;
|
||||
master->i3c_od_timing_normal = scl_timing;
|
||||
|
||||
/*
|
||||
* AMD legacy platforms need fixed OD/PP timings. Cache them as the
|
||||
* normal OD baseline so set_speed(NORMAL) restores AMD values, and
|
||||
* set_speed(SLOW) can stretch HCNT while keeping the AMD LCNT.
|
||||
*/
|
||||
if (master->quirks & AMD_I3C_OD_PP_TIMING) {
|
||||
amd_configure_od_pp_quirk(master);
|
||||
writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
|
||||
writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING);
|
||||
}
|
||||
|
||||
lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR1_SCL_RATE) - hcnt;
|
||||
scl_timing = SCL_EXT_LCNT_1(lcnt);
|
||||
@@ -613,7 +650,7 @@ static int dw_i2c_clk_cfg(struct dw_i3c_master *master)
|
||||
u16 hcnt, lcnt;
|
||||
u32 scl_timing;
|
||||
|
||||
core_rate = clk_get_rate(master->core_clk);
|
||||
core_rate = dw_i3c_master_get_core_rate(master);
|
||||
if (!core_rate)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -708,12 +745,29 @@ static void dw_i3c_master_bus_cleanup(struct i3c_master_controller *m)
|
||||
dw_i3c_master_disable(master);
|
||||
}
|
||||
|
||||
static enum i3c_error_code dw_i3c_ccc_map_err(u8 dw_err)
|
||||
{
|
||||
switch (dw_err) {
|
||||
case RESPONSE_ERROR_IBA_NACK:
|
||||
return I3C_ERROR_M2;
|
||||
case RESPONSE_ERROR_CRC:
|
||||
case RESPONSE_ERROR_PARITY:
|
||||
case RESPONSE_ERROR_FRAME:
|
||||
case RESPONSE_ERROR_TRANSF_ABORT:
|
||||
return I3C_ERROR_M0;
|
||||
default:
|
||||
return I3C_ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static int dw_i3c_ccc_set(struct dw_i3c_master *master,
|
||||
struct i3c_ccc_cmd *ccc)
|
||||
{
|
||||
struct dw_i3c_cmd *cmd;
|
||||
int ret, pos = 0;
|
||||
|
||||
ccc->err = I3C_ERROR_UNKNOWN;
|
||||
|
||||
if (ccc->id & I3C_CCC_DIRECT) {
|
||||
pos = dw_i3c_master_get_addr_pos(master, ccc->dests[0].addr);
|
||||
if (pos < 0)
|
||||
@@ -742,8 +796,8 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master,
|
||||
dw_i3c_master_dequeue_xfer(master, xfer);
|
||||
|
||||
ret = xfer->ret;
|
||||
if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK)
|
||||
ccc->err = I3C_ERROR_M2;
|
||||
cmd = &xfer->cmds[0];
|
||||
ccc->err = dw_i3c_ccc_map_err(cmd->error);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -753,6 +807,8 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
|
||||
struct dw_i3c_cmd *cmd;
|
||||
int ret, pos;
|
||||
|
||||
ccc->err = I3C_ERROR_UNKNOWN;
|
||||
|
||||
pos = dw_i3c_master_get_addr_pos(master, ccc->dests[0].addr);
|
||||
if (pos < 0)
|
||||
return pos;
|
||||
@@ -780,18 +836,14 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
|
||||
dw_i3c_master_dequeue_xfer(master, xfer);
|
||||
|
||||
ret = xfer->ret;
|
||||
if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK)
|
||||
ccc->err = I3C_ERROR_M2;
|
||||
cmd = &xfer->cmds[0];
|
||||
ccc->err = dw_i3c_ccc_map_err(cmd->error);
|
||||
if (!ret)
|
||||
ccc->dests[0].payload.actual_len = cmd->rx_len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void amd_configure_od_pp_quirk(struct dw_i3c_master *master)
|
||||
{
|
||||
master->i3c_od_timing = AMD_I3C_OD_TIMING;
|
||||
master->i3c_pp_timing = AMD_I3C_PP_TIMING;
|
||||
}
|
||||
|
||||
static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
struct i3c_ccc_cmd *ccc)
|
||||
{
|
||||
@@ -801,13 +853,6 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
if (ccc->id == I3C_CCC_ENTDAA)
|
||||
return -EINVAL;
|
||||
|
||||
/* AMD platform specific OD and PP timings */
|
||||
if (master->quirks & AMD_I3C_OD_PP_TIMING) {
|
||||
amd_configure_od_pp_quirk(master);
|
||||
writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
|
||||
writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING);
|
||||
}
|
||||
|
||||
ret = pm_runtime_resume_and_get(master->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev,
|
||||
@@ -876,7 +921,8 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
|
||||
goto rpm_out;
|
||||
}
|
||||
cmd = &xfer->cmds[0];
|
||||
cmd->cmd_hi = 0x1;
|
||||
cmd->rx_len = master->maxdevs;
|
||||
cmd->cmd_hi = COMMAND_PORT_TRANSFER_ARG;
|
||||
cmd->cmd_lo = COMMAND_PORT_DEV_COUNT(master->maxdevs - pos) |
|
||||
COMMAND_PORT_DEV_INDEX(pos) |
|
||||
COMMAND_PORT_CMD(I3C_CCC_ENTDAA) |
|
||||
@@ -888,7 +934,15 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
|
||||
if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
|
||||
dw_i3c_master_dequeue_xfer(master, xfer);
|
||||
|
||||
newdevs = GENMASK(master->maxdevs - cmd->rx_len - 1, 0);
|
||||
/*
|
||||
* cmd->rx_len holds the number of addresses ENTDAA left unassigned.
|
||||
* On an empty bus rx_len == maxdevs, so avoid GENMASK(-1, 0).
|
||||
*/
|
||||
if (cmd->rx_len >= master->maxdevs)
|
||||
newdevs = 0;
|
||||
else
|
||||
newdevs = GENMASK(master->maxdevs - cmd->rx_len - 1, 0);
|
||||
|
||||
newdevs &= ~olddevs;
|
||||
|
||||
for (pos = 0; pos < master->maxdevs; pos++) {
|
||||
@@ -1484,6 +1538,50 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int dw_i3c_master_set_speed(struct i3c_master_controller *m,
|
||||
enum i3c_open_drain_speed speed)
|
||||
{
|
||||
struct dw_i3c_master *master = to_dw_i3c_master(m);
|
||||
unsigned long core_rate;
|
||||
u32 scl_timing, od_hcnt;
|
||||
u8 lcnt;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_AUTOSUSPEND(master->dev, pm);
|
||||
if (PM_RUNTIME_ACQUIRE_ERR(&pm))
|
||||
return -ENXIO;
|
||||
|
||||
switch (speed) {
|
||||
case I3C_OPEN_DRAIN_SLOW_SPEED:
|
||||
core_rate = dw_i3c_master_get_core_rate(master);
|
||||
if (!core_rate)
|
||||
return -EINVAL;
|
||||
|
||||
lcnt = SCL_I3C_TIMING_LCNT(master->i3c_od_timing_normal);
|
||||
od_hcnt = DIV_ROUND_UP_ULL((u64)I3C_BUS_THIGH_INIT_OD_MIN_NS *
|
||||
core_rate, NSEC_PER_SEC) - 1;
|
||||
if (od_hcnt < SCL_I3C_TIMING_CNT_MIN)
|
||||
od_hcnt = SCL_I3C_TIMING_CNT_MIN;
|
||||
else if (od_hcnt > U8_MAX)
|
||||
od_hcnt = U8_MAX;
|
||||
scl_timing = SCL_I3C_TIMING_HCNT(od_hcnt) |
|
||||
SCL_I3C_TIMING_LCNT(lcnt);
|
||||
writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
|
||||
master->i3c_od_timing = scl_timing;
|
||||
break;
|
||||
|
||||
case I3C_OPEN_DRAIN_NORMAL_SPEED:
|
||||
writel(master->i3c_od_timing_normal,
|
||||
master->regs + SCL_I3C_OD_TIMING);
|
||||
master->i3c_od_timing = master->i3c_od_timing_normal;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m,
|
||||
unsigned int dev_nack_retry_cnt)
|
||||
{
|
||||
@@ -1538,6 +1636,7 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
|
||||
.recycle_ibi_slot = dw_i3c_master_recycle_ibi_slot,
|
||||
.enable_hotjoin = dw_i3c_master_enable_hotjoin,
|
||||
.disable_hotjoin = dw_i3c_master_disable_hotjoin,
|
||||
.set_speed = dw_i3c_master_set_speed,
|
||||
.set_dev_nack_retry = dw_i3c_master_set_dev_nack_retry,
|
||||
};
|
||||
|
||||
@@ -1571,20 +1670,34 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
|
||||
|
||||
master->dev = &pdev->dev;
|
||||
|
||||
if (has_acpi_companion(&pdev->dev)) {
|
||||
quirks = (unsigned long)device_get_match_data(&pdev->dev);
|
||||
} else if (pdev->dev.of_node) {
|
||||
drvdata = device_get_match_data(&pdev->dev);
|
||||
if (drvdata)
|
||||
quirks = drvdata->flags;
|
||||
}
|
||||
master->quirks = quirks;
|
||||
|
||||
master->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(master->regs))
|
||||
return PTR_ERR(master->regs);
|
||||
|
||||
master->core_clk = devm_clk_get_enabled(&pdev->dev, NULL);
|
||||
master->core_clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);
|
||||
if (IS_ERR(master->core_clk))
|
||||
return PTR_ERR(master->core_clk);
|
||||
|
||||
master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "pclk");
|
||||
if (!master->core_clk && !(master->quirks & DW_I3C_ACPI_SKIP_CLK_RST)) {
|
||||
dev_err(&pdev->dev, "missing core clock\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "apb");
|
||||
if (IS_ERR(master->pclk))
|
||||
return PTR_ERR(master->pclk);
|
||||
|
||||
master->core_rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev,
|
||||
"core_rst");
|
||||
NULL);
|
||||
if (IS_ERR(master->core_rst))
|
||||
return PTR_ERR(master->core_rst);
|
||||
|
||||
@@ -1634,15 +1747,6 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
|
||||
master->has_ibi_data = true;
|
||||
writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL);
|
||||
|
||||
if (has_acpi_companion(&pdev->dev)) {
|
||||
quirks = (unsigned long)device_get_match_data(&pdev->dev);
|
||||
} else if (pdev->dev.of_node) {
|
||||
drvdata = device_get_match_data(&pdev->dev);
|
||||
if (drvdata)
|
||||
quirks = drvdata->flags;
|
||||
}
|
||||
master->quirks = quirks;
|
||||
|
||||
/* Keep controller enabled by preventing runtime suspend */
|
||||
if (master->quirks & DW_I3C_DISABLE_RUNTIME_PM_QUIRK)
|
||||
pm_runtime_get_noresume(&pdev->dev);
|
||||
@@ -1728,10 +1832,7 @@ static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master)
|
||||
|
||||
static void dw_i3c_master_restore_timing_regs(struct dw_i3c_master *master)
|
||||
{
|
||||
/* AMD platform specific OD and PP timings */
|
||||
if (master->quirks & AMD_I3C_OD_PP_TIMING)
|
||||
amd_configure_od_pp_quirk(master);
|
||||
|
||||
/* Preserve cached OD timing; it may be the SLOW setting from set_speed(). */
|
||||
writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING);
|
||||
writel(master->bus_free_timing, master->regs + BUS_FREE_TIMING);
|
||||
writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING);
|
||||
@@ -1832,11 +1933,12 @@ 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 amd_i3c_device_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 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_i3c_device_match);
|
||||
MODULE_DEVICE_TABLE(acpi, dw_i3c_master_acpi_match);
|
||||
|
||||
static struct platform_driver dw_i3c_driver = {
|
||||
.probe = dw_i3c_probe,
|
||||
@@ -1845,7 +1947,7 @@ static struct platform_driver dw_i3c_driver = {
|
||||
.driver = {
|
||||
.name = "dw-i3c-master",
|
||||
.of_match_table = dw_i3c_master_of_match,
|
||||
.acpi_match_table = amd_i3c_device_match,
|
||||
.acpi_match_table = dw_i3c_master_acpi_match,
|
||||
.pm = &dw_i3c_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ struct dw_i3c_master {
|
||||
u32 dev_addr;
|
||||
u32 i3c_pp_timing;
|
||||
u32 i3c_od_timing;
|
||||
u32 i3c_od_timing_normal;
|
||||
u32 ext_lcnt_timing;
|
||||
u32 bus_free_timing;
|
||||
u32 i2c_fm_timing;
|
||||
|
||||
@@ -573,6 +573,7 @@ static void cdns_i3c_master_end_xfer_locked(struct cdns_i3c_master *master,
|
||||
cmd = &xfer->cmds[CMDR_CMDID(cmdr)];
|
||||
rx_len = min_t(u32, CMDR_XFER_BYTES(cmdr), cmd->rx_len);
|
||||
cdns_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
|
||||
cmd->rx_len = rx_len;
|
||||
cmd->error = CMDR_ERROR(cmdr);
|
||||
}
|
||||
|
||||
@@ -714,6 +715,8 @@ static int cdns_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
|
||||
ret = xfer->ret;
|
||||
cmd->err = cdns_i3c_cmd_get_err(&xfer->cmds[0]);
|
||||
if (!ret && cmd->rnw)
|
||||
cmd->dests[0].payload.actual_len = ccmd->rx_len;
|
||||
cdns_i3c_master_free_xfer(xfer);
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_data/mipi-i3c-hci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
@@ -117,6 +118,22 @@ static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m)
|
||||
return container_of(m, struct i3c_hci, master);
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_hci_sysdev() - Get the device to use for DMA and system PM
|
||||
* @dev: Device the HCI controller is bound to
|
||||
*
|
||||
* When an IOMMU is enabled, DMA API calls must use the device that IOMMU
|
||||
* setup was done for. Under PCI enumeration that is the PCI device, not
|
||||
* the "mipi-i3c-hci" platform device below it. The same device owns
|
||||
* system PM and wakeup configuration.
|
||||
*
|
||||
* Return: @dev's parent if it is a PCI device, otherwise @dev.
|
||||
*/
|
||||
struct device *i3c_hci_sysdev(struct device *dev)
|
||||
{
|
||||
return dev->parent && dev_is_pci(dev->parent) ? dev->parent : dev;
|
||||
}
|
||||
|
||||
static void i3c_hci_set_master_dyn_addr(struct i3c_hci *hci)
|
||||
{
|
||||
reg_write(MASTER_DEVICE_ADDR,
|
||||
@@ -375,7 +392,7 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
goto out;
|
||||
for (i = prefixed; i < nxfers; i++) {
|
||||
if (ccc->rnw)
|
||||
ccc->dests[i - prefixed].payload.len =
|
||||
ccc->dests[i - prefixed].payload.actual_len =
|
||||
RESP_DATA_LENGTH(xfer[i].response);
|
||||
switch (RESP_STATUS(xfer[i].response)) {
|
||||
case RESP_SUCCESS:
|
||||
@@ -392,7 +409,8 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
|
||||
if (ccc->rnw)
|
||||
dev_dbg(&hci->master.dev, "got: %*ph",
|
||||
ccc->dests[0].payload.len, ccc->dests[0].payload.data);
|
||||
ccc->dests[0].payload.actual_len,
|
||||
ccc->dests[0].payload.data);
|
||||
|
||||
out:
|
||||
hci_free_xfer(xfer, nxfers);
|
||||
@@ -1031,6 +1049,7 @@ static int i3c_hci_init(struct i3c_hci *hci)
|
||||
switch (regval & ~0xf) {
|
||||
case 0x100: /* version 1.0 */
|
||||
case 0x110: /* version 1.1 */
|
||||
case 0x120: /* version 1.2 */
|
||||
case 0x200: /* version 2.0 */
|
||||
break;
|
||||
default:
|
||||
@@ -1163,6 +1182,9 @@ static int i3c_hci_probe(struct platform_device *pdev)
|
||||
if (hci->quirks & HCI_QUIRK_RPM_IBI_ALLOWED)
|
||||
hci->master.rpm_ibi_allowed = true;
|
||||
|
||||
if (device_can_wakeup(i3c_hci_sysdev(&pdev->dev)))
|
||||
hci->master.ibi_wakeup = true;
|
||||
|
||||
return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false);
|
||||
}
|
||||
|
||||
@@ -1197,6 +1219,10 @@ static const struct platform_device_id i3c_hci_driver_ids[] = {
|
||||
HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET |
|
||||
HCI_QUIRK_DMA_REQUIRES_HC_ABORT,
|
||||
},
|
||||
{
|
||||
.name = "amd-pt-i3c-hci",
|
||||
.driver_data = HCI_QUIRK_RPM_ALLOWED,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <linux/errno.h>
|
||||
#include <linux/i3c/master.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "hci.h"
|
||||
#include "cmd.h"
|
||||
@@ -301,23 +300,11 @@ static int hci_dma_init(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings;
|
||||
struct hci_rh_data *rh;
|
||||
struct device *sysdev;
|
||||
u32 regval;
|
||||
unsigned int i, nr_rings, xfers_sz, resps_sz;
|
||||
unsigned int ibi_status_ring_sz, ibi_data_ring_sz;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Set pointer to a physical device that does DMA and has IOMMU setup
|
||||
* done for it in case of enabled IOMMU and use it with the DMA API.
|
||||
* Here such device is either
|
||||
* "mipi-i3c-hci" platform device (OF/ACPI enumeration) parent or
|
||||
* grandparent (PCI enumeration).
|
||||
*/
|
||||
sysdev = hci->master.dev.parent;
|
||||
if (sysdev->parent && dev_is_pci(sysdev->parent))
|
||||
sysdev = sysdev->parent;
|
||||
|
||||
regval = rhs_reg_read(CONTROL);
|
||||
nr_rings = FIELD_GET(MAX_HEADER_COUNT_CAP, regval);
|
||||
dev_dbg(&hci->master.dev, "%d DMA rings available\n", nr_rings);
|
||||
@@ -332,7 +319,7 @@ static int hci_dma_init(struct i3c_hci *hci)
|
||||
return -ENOMEM;
|
||||
hci->io_data = rings;
|
||||
rings->total = nr_rings;
|
||||
rings->sysdev = sysdev;
|
||||
rings->sysdev = i3c_hci_sysdev(hci->master.dev.parent);
|
||||
|
||||
for (i = 0; i < rings->total; i++) {
|
||||
u32 offset = rhs_reg_read(RHn_OFFSET(i));
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
#define reg_set(r, v) reg_write(r, reg_read(r) | (v))
|
||||
#define reg_clear(r, v) reg_write(r, reg_read(r) & ~(v))
|
||||
|
||||
/* helper macro for HCI version check */
|
||||
#define hci_version_at_least(hci, maj, min) \
|
||||
((hci)->version_major > (maj) || \
|
||||
((hci)->version_major == (maj) && (hci)->version_minor >= (min)))
|
||||
|
||||
struct hci_cmd_ops;
|
||||
|
||||
struct dat_words {
|
||||
@@ -184,6 +189,8 @@ void amd_set_resp_buf_thld(struct i3c_hci *hci);
|
||||
void i3c_hci_sync_irq_inactive(struct i3c_hci *hci);
|
||||
int i3c_hci_process_xfer(struct i3c_hci *hci, struct hci_xfer *xfer, int n);
|
||||
|
||||
struct device *i3c_hci_sysdev(struct device *dev);
|
||||
|
||||
#define DEFAULT_AUTOSUSPEND_DELAY_MS 1000
|
||||
|
||||
int i3c_hci_rpm_suspend(struct device *dev);
|
||||
|
||||
@@ -223,6 +223,13 @@ static const struct mipi_i3c_hci_pci_info intel_si_2_info = {
|
||||
.control_instance_pm = true,
|
||||
};
|
||||
|
||||
static const struct mipi_i3c_hci_pci_info amd_pt_info = {
|
||||
.name = "amd-pt-i3c-hci",
|
||||
.id = {0},
|
||||
.instance_offset = {0},
|
||||
.instance_count = 1,
|
||||
};
|
||||
|
||||
static int mipi_i3c_hci_pci_find_instance(struct mipi_i3c_hci_pci *hci, struct device *dev)
|
||||
{
|
||||
for (int i = 0; i < INST_MAX; i++) {
|
||||
@@ -265,6 +272,8 @@ static bool mipi_i3c_hci_pci_is_operational(struct device *dev, bool update)
|
||||
struct mipi_i3c_hci_pci_pm_data {
|
||||
struct device *dev[INST_MAX];
|
||||
int dev_cnt;
|
||||
bool can_wakeup;
|
||||
bool may_wakeup;
|
||||
};
|
||||
|
||||
static bool mipi_i3c_hci_pci_is_mfd(struct device *dev)
|
||||
@@ -272,6 +281,13 @@ static bool mipi_i3c_hci_pci_is_mfd(struct device *dev)
|
||||
return dev_is_platform(dev) && mfd_get_cell(to_platform_device(dev));
|
||||
}
|
||||
|
||||
static bool mipi_i3c_hci_pci_any_wakeup_enabled(struct device *dev)
|
||||
{
|
||||
struct i3c_hci *hci = dev_get_drvdata(dev);
|
||||
|
||||
return i3c_master_has_wakeup_enabled_devs(&hci->master);
|
||||
}
|
||||
|
||||
static int mipi_i3c_hci_pci_suspend_instance(struct device *dev, void *data)
|
||||
{
|
||||
struct mipi_i3c_hci_pci_pm_data *pm_data = data;
|
||||
@@ -287,6 +303,9 @@ static int mipi_i3c_hci_pci_suspend_instance(struct device *dev, void *data)
|
||||
|
||||
pm_data->dev[pm_data->dev_cnt++] = dev;
|
||||
|
||||
if (pm_data->can_wakeup && mipi_i3c_hci_pci_any_wakeup_enabled(dev))
|
||||
pm_data->may_wakeup = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -317,12 +336,19 @@ static int mipi_i3c_hci_pci_suspend(struct device *dev)
|
||||
if (!hci->info->control_instance_pm)
|
||||
return 0;
|
||||
|
||||
pm_data.can_wakeup = device_can_wakeup(dev);
|
||||
|
||||
ret = device_for_each_child_reverse(dev, &pm_data, mipi_i3c_hci_pci_suspend_instance);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
for (int i = 0; i < pm_data.dev_cnt; i++)
|
||||
i3c_hci_rpm_resume(pm_data.dev[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
if (device_may_wakeup(dev) != pm_data.may_wakeup)
|
||||
device_set_wakeup_enable(dev, pm_data.may_wakeup);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mipi_i3c_hci_pci_resume(struct device *dev)
|
||||
@@ -475,6 +501,8 @@ static const struct pci_device_id mipi_i3c_hci_pci_devices[] = {
|
||||
/* Nova Lake-H */
|
||||
{ PCI_VDEVICE(INTEL, 0xd37c), .driver_data = (kernel_ulong_t)&intel_mi_1_info },
|
||||
{ PCI_VDEVICE(INTEL, 0xd36f), .driver_data = (kernel_ulong_t)&intel_mi_2_info },
|
||||
/* AMD_PT */
|
||||
{ PCI_VDEVICE(AMD, 0x444c), .driver_data = (kernel_ulong_t)&amd_pt_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices);
|
||||
|
||||
@@ -45,6 +45,11 @@
|
||||
#define IBI_STATUS_SIZE GENMASK(15, 8)
|
||||
#define CR_QUEUE_SIZE GENMASK(7, 0)
|
||||
|
||||
#define PIO_ALT_QUEUE_SIZE 0x1C
|
||||
#define EXT_IBI_QUEUE_EN BIT(28)
|
||||
#define ALT_RESP_QUEUE_EN BIT(24)
|
||||
#define ALT_RESP_QUEUE_SIZE GENMASK(7, 0)
|
||||
|
||||
#define PIO_INTR_STATUS 0x20
|
||||
#define PIO_INTR_STATUS_ENABLE 0x24
|
||||
#define PIO_INTR_SIGNAL_ENABLE 0x28
|
||||
@@ -72,6 +77,11 @@
|
||||
#define STAT_RX_THLD BIT(1)
|
||||
#define STAT_TX_THLD BIT(0)
|
||||
|
||||
#define PIO_CONTROL 0x30
|
||||
#define PIO_CONTROL_ABORT BIT(2)
|
||||
#define PIO_CONTROL_RS BIT(1)
|
||||
#define PIO_CONTROL_ENABLE BIT(0)
|
||||
|
||||
#define PIO_QUEUE_CUR_STATUS 0x38
|
||||
#define CUR_IBI_Q_LEVEL GENMASK(28, 20)
|
||||
#define CUR_RESP_Q_LEVEL GENMASK(18, 10)
|
||||
@@ -173,6 +183,14 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr)
|
||||
* IBI queue size within allowed bounds.
|
||||
*/
|
||||
ibi_val = FIELD_GET(IBI_STATUS_SIZE, size_val);
|
||||
/* Adjust actual IBI queue size based on v1.2 ALT_QUEUE_SIZE */
|
||||
if (hci_version_at_least(hci, 1, 2)) {
|
||||
u32 alt_val = pio_reg_read(ALT_QUEUE_SIZE);
|
||||
|
||||
if (alt_val & EXT_IBI_QUEUE_EN)
|
||||
ibi_val *= 8;
|
||||
}
|
||||
|
||||
pio->max_ibi_thresh = clamp_val(ibi_val/2, 1, 63);
|
||||
val = FIELD_PREP(QUEUE_IBI_STATUS_THLD, 1) |
|
||||
FIELD_PREP(QUEUE_IBI_DATA_THLD, pio->max_ibi_thresh) |
|
||||
@@ -185,8 +203,22 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr)
|
||||
pio_reg_write(INTR_SIGNAL_ENABLE, 0x0);
|
||||
pio_reg_write(INTR_STATUS_ENABLE, 0xffffffff);
|
||||
|
||||
/* Always accept error interrupts (will be activated on first xfer) */
|
||||
pio->enabled_irqs = STAT_ALL_ERRORS;
|
||||
/*
|
||||
* Always accept error interrupts and IBI threshold interrupt
|
||||
* (will be activated on first xfer).
|
||||
*/
|
||||
pio->enabled_irqs = STAT_ALL_ERRORS | STAT_IBI_STATUS_THLD;
|
||||
|
||||
/* MIPI I3C HCI v1.2 requires explicitly enabling and starting PIO queues */
|
||||
if (hci_version_at_least(hci, 1, 2)) {
|
||||
u32 ctl_val = pio_reg_read(CONTROL);
|
||||
|
||||
if (!(ctl_val & PIO_CONTROL_ENABLE)) {
|
||||
ctl_val |= PIO_CONTROL_ENABLE;
|
||||
pio_reg_write(CONTROL, ctl_val);
|
||||
}
|
||||
pio_reg_write(CONTROL, ctl_val | PIO_CONTROL_RS);
|
||||
}
|
||||
}
|
||||
|
||||
static void hci_pio_suspend(struct i3c_hci *hci)
|
||||
@@ -205,6 +237,7 @@ static int hci_pio_init(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_pio_data *pio;
|
||||
u32 size_val;
|
||||
u32 cmd_sz, resp_sz, ibi_val;
|
||||
|
||||
pio = devm_kzalloc(hci->master.dev.parent, sizeof(*pio), GFP_KERNEL);
|
||||
if (!pio)
|
||||
@@ -214,10 +247,24 @@ static int hci_pio_init(struct i3c_hci *hci)
|
||||
|
||||
__hci_pio_init(hci, &size_val);
|
||||
|
||||
dev_dbg(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n",
|
||||
FIELD_GET(CR_QUEUE_SIZE, size_val));
|
||||
dev_dbg(&hci->master.dev, "IBI FIFO = %ld bytes\n",
|
||||
4 * FIELD_GET(IBI_STATUS_SIZE, size_val));
|
||||
cmd_sz = FIELD_GET(CR_QUEUE_SIZE, size_val);
|
||||
resp_sz = cmd_sz;
|
||||
ibi_val = FIELD_GET(IBI_STATUS_SIZE, size_val);
|
||||
|
||||
/* MIPI I3C HCI v1.2 supports alternate RESP/IBI queue size */
|
||||
if (hci_version_at_least(hci, 1, 2)) {
|
||||
u32 alt_val = pio_reg_read(ALT_QUEUE_SIZE);
|
||||
|
||||
if (alt_val & ALT_RESP_QUEUE_EN)
|
||||
resp_sz = FIELD_GET(ALT_RESP_QUEUE_SIZE, alt_val);
|
||||
if (alt_val & EXT_IBI_QUEUE_EN)
|
||||
ibi_val *= 8;
|
||||
}
|
||||
|
||||
dev_dbg(&hci->master.dev, "CMD FIFO = %u, RESP FIFO = %u entries\n",
|
||||
cmd_sz, resp_sz);
|
||||
dev_dbg(&hci->master.dev, "IBI FIFO = %u bytes\n",
|
||||
4 * ibi_val);
|
||||
dev_dbg(&hci->master.dev, "RX data FIFO = %d bytes\n",
|
||||
4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val)));
|
||||
dev_dbg(&hci->master.dev, "TX data FIFO = %d bytes\n",
|
||||
@@ -241,6 +288,9 @@ static void hci_pio_cleanup(struct i3c_hci *hci)
|
||||
BUG_ON(pio->curr_rx);
|
||||
BUG_ON(pio->curr_tx);
|
||||
BUG_ON(pio->curr_resp);
|
||||
/* MIPI I3C HCI v1.2 requires explicitly stopping and disabling PIO queues */
|
||||
if (hci_version_at_least(hci, 1, 2))
|
||||
pio_reg_write(CONTROL, 0x0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,6 +811,18 @@ static void hci_pio_err(struct i3c_hci *hci, struct hci_pio_data *pio,
|
||||
hci_pio_dequeue_xfer_common(hci, pio, pio->curr_tx, 1);
|
||||
/* then reset the hardware */
|
||||
mipi_i3c_hci_pio_reset(hci);
|
||||
|
||||
/* MIPI I3C HCI v1.2 requires explicitly restarting PIO queues after error/abort */
|
||||
if (hci_version_at_least(hci, 1, 2)) {
|
||||
u32 ctl_val = pio_reg_read(CONTROL);
|
||||
|
||||
if (!(ctl_val & PIO_CONTROL_ENABLE)) {
|
||||
ctl_val |= PIO_CONTROL_ENABLE;
|
||||
pio_reg_write(CONTROL, ctl_val);
|
||||
}
|
||||
pio_reg_write(CONTROL, ctl_val | PIO_CONTROL_RS);
|
||||
}
|
||||
|
||||
mipi_i3c_hci_resume(hci);
|
||||
|
||||
dev_dbg(&hci->master.dev, "status=%#x/%#x",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
#include "../internals.h"
|
||||
@@ -109,7 +110,7 @@
|
||||
#define NCMDQP_DATA_LENGTH(x) FIELD_PREP(GENMASK(31, 16), x)
|
||||
|
||||
#define NRSPQP 0x154 /* Normal Respone Queue */
|
||||
#define NRSPQP_NO_ERROR 0
|
||||
#define NRSPQP_NO_ERROR 0
|
||||
#define NRSPQP_ERROR_CRC 1
|
||||
#define NRSPQP_ERROR_PARITY 2
|
||||
#define NRSPQP_ERROR_FRAME 3
|
||||
@@ -199,8 +200,6 @@
|
||||
#define RENESAS_I3C_MAX_DEVS 8
|
||||
#define I2C_INIT_MSG -1
|
||||
|
||||
#define RENESAS_I3C_TCLK_IDX 1
|
||||
|
||||
enum i3c_internal_state {
|
||||
I3C_INTERNAL_STATE_DISABLED,
|
||||
I3C_INTERNAL_STATE_CONTROLLER_IDLE,
|
||||
@@ -221,19 +220,19 @@ enum renesas_i3c_event {
|
||||
};
|
||||
|
||||
struct renesas_i3c_cmd {
|
||||
const void *tx_buf;
|
||||
void *rx_buf;
|
||||
/* i2c xfer */
|
||||
u8 *i2c_buf;
|
||||
const struct i2c_msg *msg;
|
||||
int i2c_bytes_left;
|
||||
int i2c_is_last;
|
||||
u32 cmd0;
|
||||
u32 len;
|
||||
const void *tx_buf;
|
||||
u32 tx_count;
|
||||
void *rx_buf;
|
||||
u32 rx_count;
|
||||
u32 err;
|
||||
u8 rnw;
|
||||
/* i2c xfer */
|
||||
int i2c_bytes_left;
|
||||
int i2c_is_last;
|
||||
u8 *i2c_buf;
|
||||
const struct i2c_msg *msg;
|
||||
};
|
||||
|
||||
struct renesas_i3c_xfer {
|
||||
@@ -253,22 +252,22 @@ struct renesas_i3c_xferqueue {
|
||||
};
|
||||
|
||||
struct renesas_i3c {
|
||||
void __iomem *regs;
|
||||
struct clk *tclk;
|
||||
struct reset_control *presetn;
|
||||
struct reset_control *tresetn;
|
||||
struct device *dev;
|
||||
struct renesas_i3c_xferqueue xferqueue;
|
||||
struct i3c_master_controller base;
|
||||
u8 addrs[RENESAS_I3C_MAX_DEVS];
|
||||
unsigned long rate;
|
||||
enum i3c_internal_state internal_state;
|
||||
u16 maxdevs;
|
||||
u32 free_pos;
|
||||
u32 dyn_addr;
|
||||
u32 i2c_STDBR;
|
||||
u32 i3c_STDBR;
|
||||
unsigned long rate;
|
||||
u8 addrs[RENESAS_I3C_MAX_DEVS];
|
||||
struct renesas_i3c_xferqueue xferqueue;
|
||||
void __iomem *regs;
|
||||
u32 *DATBASn;
|
||||
struct clk_bulk_data *clks;
|
||||
struct reset_control *presetn;
|
||||
struct reset_control *tresetn;
|
||||
u8 num_clks;
|
||||
u32 extbr;
|
||||
u16 maxdevs;
|
||||
u8 refclk_div;
|
||||
};
|
||||
|
||||
@@ -433,7 +432,29 @@ static void renesas_i3c_enqueue_xfer(struct renesas_i3c *i3c, struct renesas_i3c
|
||||
}
|
||||
}
|
||||
|
||||
static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer)
|
||||
static void renesas_i3c_irqs_mask_and_clear_locked(struct renesas_i3c *i3c)
|
||||
{
|
||||
/* Disable all the interrupts. */
|
||||
renesas_writel(i3c->regs, BIE, 0);
|
||||
renesas_writel(i3c->regs, NTIE, 0);
|
||||
|
||||
/* Clear normal transfer status flags. */
|
||||
renesas_writel(i3c->regs, NTST, 0);
|
||||
|
||||
/* Clear bus status flags. */
|
||||
renesas_writel(i3c->regs, BST, 0);
|
||||
/* Read back registers to confirm writes have fully propagated. */
|
||||
renesas_readl(i3c->regs, BST);
|
||||
}
|
||||
|
||||
static void renesas_i3c_irqs_mask_and_clear(struct renesas_i3c *i3c)
|
||||
{
|
||||
guard(spinlock_irqsave)(&i3c->xferqueue.lock);
|
||||
|
||||
renesas_i3c_irqs_mask_and_clear_locked(i3c);
|
||||
}
|
||||
|
||||
static unsigned long renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer)
|
||||
{
|
||||
unsigned long time_left;
|
||||
|
||||
@@ -442,6 +463,8 @@ static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xf
|
||||
time_left = wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000));
|
||||
if (!time_left)
|
||||
renesas_i3c_dequeue_xfer(i3c, xfer);
|
||||
|
||||
return time_left;
|
||||
}
|
||||
|
||||
static void renesas_i3c_set_prts(struct renesas_i3c *i3c, u32 val)
|
||||
@@ -475,6 +498,12 @@ static void renesas_i3c_bus_enable(struct i3c_master_controller *m, bool i3c_mod
|
||||
static int renesas_i3c_reset(struct renesas_i3c *i3c)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
renesas_writel(i3c->regs, BCTL, 0);
|
||||
renesas_set_bit(i3c->regs, RSTCTL, RSTCTL_RI3CRST);
|
||||
@@ -546,14 +575,10 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
|
||||
int od_high_ticks, od_low_ticks, i2c_total_ticks;
|
||||
int ret;
|
||||
|
||||
i3c->rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk);
|
||||
i3c->rate = clk_get_rate(i3c->tclk);
|
||||
if (!i3c->rate)
|
||||
return -EINVAL;
|
||||
|
||||
ret = renesas_i3c_reset(i3c);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i2c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i2c);
|
||||
i3c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i3c);
|
||||
|
||||
@@ -604,29 +629,36 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
|
||||
STDBR_SBRHO(double_SBR, od_high_ticks) |
|
||||
STDBR_SBRLP(pp_low_ticks) |
|
||||
STDBR_SBRHP(pp_high_ticks);
|
||||
renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
|
||||
|
||||
/* Extended Bit Rate setting */
|
||||
renesas_writel(i3c->regs, EXTBR, EXTBR_EBRLO(od_low_ticks) |
|
||||
EXTBR_EBRHO(od_high_ticks) |
|
||||
EXTBR_EBRLP(pp_low_ticks) |
|
||||
EXTBR_EBRHP(pp_high_ticks));
|
||||
|
||||
renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
|
||||
i3c->refclk_div = cks;
|
||||
|
||||
/* I3C hw init*/
|
||||
renesas_i3c_hw_init(i3c);
|
||||
i3c->extbr = EXTBR_EBRLO(od_low_ticks) | EXTBR_EBRHO(od_high_ticks) |
|
||||
EXTBR_EBRLP(pp_low_ticks) | EXTBR_EBRHP(pp_high_ticks);
|
||||
|
||||
ret = i3c_master_get_free_addr(m, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
i3c->dyn_addr = ret;
|
||||
renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.dyn_addr = ret;
|
||||
i3c->dyn_addr = ret;
|
||||
i3c->refclk_div = cks;
|
||||
|
||||
ret = renesas_i3c_reset(i3c);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
|
||||
renesas_writel(i3c->regs, EXTBR, i3c->extbr);
|
||||
renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
|
||||
renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(i3c->dyn_addr) | MSDVAD_MDYADV);
|
||||
|
||||
/* I3C hw init*/
|
||||
renesas_i3c_hw_init(i3c);
|
||||
|
||||
return i3c_master_set_info(&i3c->base, &info);
|
||||
}
|
||||
|
||||
@@ -641,6 +673,7 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
|
||||
{
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct renesas_i3c_cmd *cmd;
|
||||
unsigned long time_left;
|
||||
u32 olddevs, newdevs;
|
||||
u8 last_addr = 0, pos;
|
||||
int ret;
|
||||
@@ -649,6 +682,15 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
|
||||
if (!xfer)
|
||||
return -ENOMEM;
|
||||
|
||||
init_completion(&xfer->comp);
|
||||
cmd = xfer->cmds;
|
||||
cmd->rx_count = i3c->maxdevs;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable I3C bus. */
|
||||
renesas_i3c_bus_enable(m, true);
|
||||
|
||||
@@ -670,10 +712,6 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
|
||||
renesas_writel(i3c->regs, DATBAS(pos), datbas_dvdyad_with_parity(ret));
|
||||
}
|
||||
|
||||
init_completion(&xfer->comp);
|
||||
cmd = xfer->cmds;
|
||||
cmd->rx_count = 0;
|
||||
|
||||
ret = renesas_i3c_get_free_pos(i3c);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -687,9 +725,15 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
|
||||
NCMDQP_CMD(I3C_CCC_ENTDAA) | NCMDQP_DEV_INDEX(ret) |
|
||||
NCMDQP_DEV_COUNT(i3c->maxdevs - ret) | NCMDQP_TOC;
|
||||
|
||||
renesas_i3c_wait_xfer(i3c, xfer);
|
||||
time_left = renesas_i3c_wait_xfer(i3c, xfer);
|
||||
if (!time_left)
|
||||
renesas_i3c_irqs_mask_and_clear(i3c);
|
||||
|
||||
if (cmd->rx_count >= i3c->maxdevs)
|
||||
newdevs = 0;
|
||||
else
|
||||
newdevs = GENMASK(i3c->maxdevs - cmd->rx_count - 1, 0);
|
||||
|
||||
newdevs = GENMASK(i3c->maxdevs - cmd->rx_count - 1, 0);
|
||||
newdevs &= ~olddevs;
|
||||
|
||||
for (pos = 0; pos < i3c->maxdevs; pos++) {
|
||||
@@ -749,6 +793,7 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
{
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct renesas_i3c_cmd *cmd;
|
||||
unsigned long time_left;
|
||||
int ret, pos = 0;
|
||||
|
||||
if (ccc->id & I3C_CCC_DIRECT) {
|
||||
@@ -761,13 +806,18 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
if (!xfer)
|
||||
return -ENOMEM;
|
||||
|
||||
renesas_i3c_bus_enable(m, true);
|
||||
|
||||
init_completion(&xfer->comp);
|
||||
cmd = xfer->cmds;
|
||||
cmd->rnw = ccc->rnw;
|
||||
cmd->cmd0 = 0;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
renesas_i3c_bus_enable(m, true);
|
||||
|
||||
/* Calculate the command descriptor. */
|
||||
switch (ccc->id) {
|
||||
case I3C_CCC_SETDASA:
|
||||
@@ -800,11 +850,15 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
|
||||
}
|
||||
}
|
||||
|
||||
renesas_i3c_wait_xfer(i3c, xfer);
|
||||
time_left = renesas_i3c_wait_xfer(i3c, xfer);
|
||||
if (!time_left)
|
||||
renesas_i3c_irqs_mask_and_clear(i3c);
|
||||
|
||||
ret = xfer->ret;
|
||||
if (ret)
|
||||
ccc->err = I3C_ERROR_M2;
|
||||
else if (ccc->rnw)
|
||||
ccc->dests[0].payload.actual_len = cmd->rx_count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -815,10 +869,9 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
||||
int i;
|
||||
|
||||
/* Enable I3C bus. */
|
||||
renesas_i3c_bus_enable(m, true);
|
||||
unsigned long time_left;
|
||||
bool xfer_failed = false;
|
||||
int i, ret;
|
||||
|
||||
struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1);
|
||||
if (!xfer)
|
||||
@@ -826,6 +879,14 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
|
||||
|
||||
init_completion(&xfer->comp);
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable I3C bus. */
|
||||
renesas_i3c_bus_enable(m, true);
|
||||
|
||||
for (i = 0; i < i3c_nxfers; i++) {
|
||||
struct renesas_i3c_cmd *cmd = xfer->cmds;
|
||||
|
||||
@@ -854,9 +915,14 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
|
||||
renesas_set_bit(i3c->regs, NTIE, NTIE_TDBEIE0);
|
||||
}
|
||||
|
||||
renesas_i3c_wait_xfer(i3c, xfer);
|
||||
time_left = renesas_i3c_wait_xfer(i3c, xfer);
|
||||
if (!time_left)
|
||||
xfer_failed = true;
|
||||
}
|
||||
|
||||
if (xfer_failed)
|
||||
renesas_i3c_irqs_mask_and_clear(i3c);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -865,12 +931,17 @@ static int renesas_i3c_attach_i3c_dev(struct i3c_dev_desc *dev)
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct renesas_i3c_i2c_dev_data *data;
|
||||
int pos;
|
||||
int pos, ret;
|
||||
|
||||
pos = renesas_i3c_get_free_pos(i3c);
|
||||
if (pos < 0)
|
||||
return pos;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data = kzalloc_obj(*data);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
@@ -892,10 +963,31 @@ static int renesas_i3c_reattach_i3c_dev(struct i3c_dev_desc *dev,
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
||||
int pos, ret;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pos = renesas_i3c_get_free_pos(i3c);
|
||||
|
||||
if (data->index > pos && pos >= 0) {
|
||||
renesas_writel(i3c->regs, DATBAS(data->index), 0);
|
||||
i3c->addrs[data->index] = 0;
|
||||
i3c->free_pos |= BIT(data->index);
|
||||
|
||||
data->index = pos;
|
||||
i3c->free_pos &= ~BIT(data->index);
|
||||
}
|
||||
|
||||
i3c->addrs[data->index] = dev->info.dyn_addr ? dev->info.dyn_addr :
|
||||
dev->info.static_addr;
|
||||
|
||||
renesas_writel(i3c->regs, DATBAS(data->index),
|
||||
DATBAS_DVSTAD(dev->info.static_addr) |
|
||||
datbas_dvdyad_with_parity(i3c->addrs[data->index]));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -904,6 +996,12 @@ static void renesas_i3c_detach_i3c_dev(struct i3c_dev_desc *dev)
|
||||
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
int ret;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (!ret)
|
||||
renesas_writel(i3c->regs, DATBAS(data->index), 0);
|
||||
|
||||
i3c_dev_set_master_data(dev, NULL);
|
||||
i3c->addrs[data->index] = 0;
|
||||
@@ -919,21 +1017,28 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct renesas_i3c_cmd *cmd;
|
||||
u8 start_bit = CNDCTL_STCND;
|
||||
int i;
|
||||
unsigned long time_left;
|
||||
bool xfer_failed = false;
|
||||
int i, ret;
|
||||
|
||||
if (!i2c_nxfers)
|
||||
return 0;
|
||||
|
||||
struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1);
|
||||
if (!xfer)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!i2c_nxfers)
|
||||
return 0;
|
||||
|
||||
renesas_i3c_bus_enable(m, false);
|
||||
|
||||
init_completion(&xfer->comp);
|
||||
xfer->is_i2c_xfer = true;
|
||||
cmd = xfer->cmds;
|
||||
|
||||
PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
|
||||
ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
renesas_i3c_bus_enable(m, false);
|
||||
|
||||
if (!(renesas_readl(i3c->regs, BCST) & BCST_BFREF)) {
|
||||
cmd->err = -EBUSY;
|
||||
return cmd->err;
|
||||
@@ -958,7 +1063,9 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
|
||||
|
||||
renesas_set_bit(i3c->regs, NTSTE, NTSTE_TDBEE0);
|
||||
|
||||
wait_for_completion_timeout(&xfer->comp, m->i2c.timeout);
|
||||
time_left = wait_for_completion_timeout(&xfer->comp, m->i2c.timeout);
|
||||
if (!time_left)
|
||||
xfer_failed = true;
|
||||
|
||||
if (cmd->err)
|
||||
break;
|
||||
@@ -967,6 +1074,10 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
|
||||
}
|
||||
|
||||
renesas_i3c_dequeue_xfer(i3c, xfer);
|
||||
|
||||
if (xfer_failed)
|
||||
renesas_i3c_irqs_mask_and_clear(i3c);
|
||||
|
||||
return cmd->err;
|
||||
}
|
||||
|
||||
@@ -1014,6 +1125,11 @@ static irqreturn_t renesas_i3c_tx_isr(int irq, void *data)
|
||||
|
||||
scoped_guard(spinlock, &i3c->xferqueue.lock) {
|
||||
xfer = i3c->xferqueue.cur;
|
||||
if (!xfer) {
|
||||
renesas_i3c_irqs_mask_and_clear_locked(i3c);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
cmd = xfer->cmds;
|
||||
|
||||
if (xfer->is_i2c_xfer) {
|
||||
@@ -1054,6 +1170,11 @@ static irqreturn_t renesas_i3c_resp_isr(int irq, void *data)
|
||||
|
||||
scoped_guard(spinlock, &i3c->xferqueue.lock) {
|
||||
xfer = i3c->xferqueue.cur;
|
||||
if (!xfer) {
|
||||
renesas_i3c_irqs_mask_and_clear_locked(i3c);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
cmd = xfer->cmds;
|
||||
|
||||
/* Clear the Respone Queue Full status flag*/
|
||||
@@ -1072,10 +1193,16 @@ static irqreturn_t renesas_i3c_resp_isr(int irq, void *data)
|
||||
break;
|
||||
case I3C_INTERNAL_STATE_CONTROLLER_READ:
|
||||
case I3C_INTERNAL_STATE_CONTROLLER_COMMAND_READ:
|
||||
if (NDBSTLV0_RDBLV(renesas_readl(i3c->regs, NDBSTLV0)) && !cmd->err)
|
||||
bytes_remaining = data_len - cmd->rx_count;
|
||||
if (!cmd->err) {
|
||||
u32 rx_count = min(cmd->rx_count, data_len);
|
||||
|
||||
i3c_readl_fifo(i3c->regs + NTDTBP0, cmd->rx_buf, bytes_remaining);
|
||||
bytes_remaining = data_len - rx_count;
|
||||
if (bytes_remaining)
|
||||
i3c_readl_fifo(i3c->regs + NTDTBP0,
|
||||
cmd->rx_buf + rx_count,
|
||||
bytes_remaining);
|
||||
cmd->rx_count = data_len;
|
||||
}
|
||||
renesas_clear_bit(i3c->regs, NTIE, NTIE_RDBFIE0);
|
||||
break;
|
||||
default:
|
||||
@@ -1138,6 +1265,11 @@ static irqreturn_t renesas_i3c_tend_isr(int irq, void *data)
|
||||
|
||||
scoped_guard(spinlock, &i3c->xferqueue.lock) {
|
||||
xfer = i3c->xferqueue.cur;
|
||||
if (!xfer) {
|
||||
renesas_i3c_irqs_mask_and_clear_locked(i3c);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
cmd = xfer->cmds;
|
||||
|
||||
if (xfer->is_i2c_xfer) {
|
||||
@@ -1184,6 +1316,11 @@ static irqreturn_t renesas_i3c_rx_isr(int irq, void *data)
|
||||
|
||||
scoped_guard(spinlock, &i3c->xferqueue.lock) {
|
||||
xfer = i3c->xferqueue.cur;
|
||||
if (!xfer) {
|
||||
renesas_i3c_irqs_mask_and_clear_locked(i3c);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
cmd = xfer->cmds;
|
||||
|
||||
if (xfer->is_i2c_xfer) {
|
||||
@@ -1234,15 +1371,13 @@ static irqreturn_t renesas_i3c_stop_isr(int irq, void *data)
|
||||
struct renesas_i3c_xfer *xfer;
|
||||
|
||||
scoped_guard(spinlock, &i3c->xferqueue.lock) {
|
||||
xfer = i3c->xferqueue.cur;
|
||||
|
||||
/* read back registers to confirm writes have fully propagated */
|
||||
renesas_writel(i3c->regs, BST, 0);
|
||||
renesas_readl(i3c->regs, BST);
|
||||
renesas_writel(i3c->regs, BIE, 0);
|
||||
renesas_clear_bit(i3c->regs, NTST, NTST_TDBEF0 | NTST_RDBFF0);
|
||||
renesas_i3c_irqs_mask_and_clear_locked(i3c);
|
||||
renesas_clear_bit(i3c->regs, SCSTRCTL, SCSTRCTL_RWE);
|
||||
|
||||
xfer = i3c->xferqueue.cur;
|
||||
if (!xfer)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
xfer->ret = 0;
|
||||
complete(&xfer->comp);
|
||||
}
|
||||
@@ -1259,6 +1394,11 @@ static irqreturn_t renesas_i3c_start_isr(int irq, void *data)
|
||||
|
||||
scoped_guard(spinlock, &i3c->xferqueue.lock) {
|
||||
xfer = i3c->xferqueue.cur;
|
||||
if (!xfer) {
|
||||
renesas_i3c_irqs_mask_and_clear_locked(i3c);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
cmd = xfer->cmds;
|
||||
|
||||
if (xfer->is_i2c_xfer) {
|
||||
@@ -1325,12 +1465,16 @@ static int renesas_i3c_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(i3c->regs))
|
||||
return PTR_ERR(i3c->regs);
|
||||
|
||||
ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &i3c->clks);
|
||||
if (ret <= RENESAS_I3C_TCLK_IDX)
|
||||
return dev_err_probe(&pdev->dev, ret < 0 ? ret : -EINVAL,
|
||||
"Failed to get clocks (need > %d, got %d)\n",
|
||||
RENESAS_I3C_TCLK_IDX, ret);
|
||||
i3c->num_clks = ret;
|
||||
i3c->tclk = devm_clk_get(&pdev->dev, "tclk");
|
||||
if (IS_ERR(i3c->tclk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tclk), "Failed to get tclk");
|
||||
|
||||
i3c->dev = &pdev->dev;
|
||||
pm_runtime_set_autosuspend_delay(&pdev->dev, 300);
|
||||
pm_runtime_use_autosuspend(&pdev->dev);
|
||||
ret = devm_pm_runtime_enable(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
|
||||
if (IS_ERR(i3c->tresetn))
|
||||
@@ -1350,12 +1494,19 @@ static int renesas_i3c_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(renesas_i3c_irqs); i++) {
|
||||
const char *irqname;
|
||||
|
||||
ret = platform_get_irq_byname(pdev, renesas_i3c_irqs[i].name);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
irqname = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s:%s", dev_name(&pdev->dev),
|
||||
renesas_i3c_irqs[i].desc);
|
||||
if (!irqname)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, ret, renesas_i3c_irqs[i].isr,
|
||||
0, renesas_i3c_irqs[i].desc, i3c);
|
||||
0, irqname, i3c);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@@ -1365,12 +1516,6 @@ static int renesas_i3c_probe(struct platform_device *pdev)
|
||||
i3c->maxdevs = RENESAS_I3C_MAX_DEVS;
|
||||
i3c->free_pos = GENMASK(i3c->maxdevs - 1, 0);
|
||||
|
||||
/* Allocate dynamic Device Address Table backup. */
|
||||
i3c->DATBASn = devm_kzalloc(&pdev->dev, sizeof(u32) * i3c->maxdevs,
|
||||
GFP_KERNEL);
|
||||
if (!i3c->DATBASn)
|
||||
return -ENOMEM;
|
||||
|
||||
return i3c_master_register(&i3c->base, &pdev->dev, &renesas_i3c_ops, false);
|
||||
}
|
||||
|
||||
@@ -1381,81 +1526,90 @@ static void renesas_i3c_remove(struct platform_device *pdev)
|
||||
i3c_master_unregister(&i3c->base);
|
||||
}
|
||||
|
||||
static int renesas_i3c_suspend_noirq(struct device *dev)
|
||||
static int renesas_i3c_suspend(struct device *dev)
|
||||
{
|
||||
struct renesas_i3c *i3c = dev_get_drvdata(dev);
|
||||
int i, ret;
|
||||
struct reset_control_bulk_data resets[] = {
|
||||
{ .rstc = i3c->presetn },
|
||||
{ .rstc = i3c->tresetn },
|
||||
};
|
||||
int ret;
|
||||
|
||||
i2c_mark_adapter_suspended(&i3c->base.i2c);
|
||||
|
||||
/* Store Device Address Table values. */
|
||||
for (i = 0; i < i3c->maxdevs; i++)
|
||||
i3c->DATBASn[i] = renesas_readl(i3c->regs, DATBAS(i));
|
||||
|
||||
ret = reset_control_assert(i3c->presetn);
|
||||
ret = reset_control_bulk_assert(ARRAY_SIZE(resets), resets);
|
||||
if (ret)
|
||||
goto err_mark_resumed;
|
||||
|
||||
ret = reset_control_assert(i3c->tresetn);
|
||||
if (ret)
|
||||
goto err_presetn;
|
||||
|
||||
clk_bulk_disable(i3c->num_clks, i3c->clks);
|
||||
|
||||
return 0;
|
||||
|
||||
err_presetn:
|
||||
reset_control_deassert(i3c->presetn);
|
||||
err_mark_resumed:
|
||||
i2c_mark_adapter_resumed(&i3c->base.i2c);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int renesas_i3c_resume_noirq(struct device *dev)
|
||||
static int renesas_i3c_resume(struct device *dev)
|
||||
{
|
||||
struct renesas_i3c *i3c = dev_get_drvdata(dev);
|
||||
int i, ret;
|
||||
struct reset_control_bulk_data resets[] = {
|
||||
{ .rstc = i3c->presetn },
|
||||
{ .rstc = i3c->tresetn },
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = reset_control_deassert(i3c->presetn);
|
||||
ret = reset_control_bulk_deassert(ARRAY_SIZE(resets), resets);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_control_deassert(i3c->tresetn);
|
||||
ret = renesas_i3c_reset(i3c);
|
||||
if (ret)
|
||||
goto err_presetn;
|
||||
goto err_resets_asserted;
|
||||
|
||||
ret = clk_bulk_enable(i3c->num_clks, i3c->clks);
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
goto err_tresetn;
|
||||
goto err_resets_asserted;
|
||||
|
||||
/* Re-store I3C registers value. */
|
||||
renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
|
||||
renesas_writel(i3c->regs, EXTBR, i3c->extbr);
|
||||
renesas_writel(i3c->regs, REFCKCTL,
|
||||
REFCKCTL_IREFCKS(i3c->refclk_div));
|
||||
renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYADV |
|
||||
MSDVAD_MDYAD(i3c->dyn_addr));
|
||||
|
||||
/* Restore Device Address Table values. */
|
||||
for (i = 0; i < i3c->maxdevs; i++)
|
||||
renesas_writel(i3c->regs, DATBAS(i), i3c->DATBASn[i]);
|
||||
|
||||
/* I3C hw init. */
|
||||
renesas_i3c_hw_init(i3c);
|
||||
|
||||
ret = i3c_master_do_daa_ext(&i3c->base, true);
|
||||
if (ret)
|
||||
dev_err(dev, "DAA failed on resume, ret=%d", ret);
|
||||
|
||||
i2c_mark_adapter_resumed(&i3c->base.i2c);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
/*
|
||||
* I3C devices may have retained their dynamic address anyway. Do not
|
||||
* fail the resume because of DAA error.
|
||||
*/
|
||||
return 0;
|
||||
|
||||
err_tresetn:
|
||||
reset_control_assert(i3c->tresetn);
|
||||
err_presetn:
|
||||
reset_control_assert(i3c->presetn);
|
||||
err_resets_asserted:
|
||||
/*
|
||||
* If this happens, there is no way to recover from this state without
|
||||
* reloading the driver. We want to avoid keeping the reset line
|
||||
* deasserted unnecessarily. The runtime paths will still work correctly
|
||||
* even if the IP registers are accessed while reset is asserted (e.g.
|
||||
* if a runtime path is triggered after a failed resume). Checked on
|
||||
* RZ/G3S.
|
||||
*/
|
||||
reset_control_bulk_assert(ARRAY_SIZE(resets), resets);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops renesas_i3c_pm_ops = {
|
||||
NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq,
|
||||
renesas_i3c_resume_noirq)
|
||||
SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend, renesas_i3c_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id renesas_i3c_of_ids[] = {
|
||||
|
||||
@@ -455,14 +455,22 @@ static int svc_i3c_master_handle_ibi(struct svc_i3c_master *master,
|
||||
buf = slot->data;
|
||||
|
||||
while (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)) &&
|
||||
slot->len < SVC_I3C_FIFO_SIZE) {
|
||||
slot->len < dev->ibi->max_payload_len) {
|
||||
mdatactrl = readl(master->regs + SVC_I3C_MDATACTRL);
|
||||
count = SVC_I3C_MDATACTRL_RXCOUNT(mdatactrl);
|
||||
count = min(count, dev->ibi->max_payload_len - slot->len);
|
||||
readsb(master->regs + SVC_I3C_MRDATAB, buf, count);
|
||||
slot->len += count;
|
||||
buf += count;
|
||||
}
|
||||
|
||||
/*
|
||||
* The device may have sent more than the requested payload. Drop the
|
||||
* extra bytes so they do not leak into the next transfer.
|
||||
*/
|
||||
if (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)))
|
||||
writel(SVC_I3C_MDATACTRL_FLUSHRB, master->regs + SVC_I3C_MDATACTRL);
|
||||
|
||||
master->ibi.tbq_slot = slot;
|
||||
|
||||
return 0;
|
||||
@@ -1488,8 +1496,11 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
||||
svc_i3c_master_emit_force_exit(master);
|
||||
|
||||
/* Wait idle if stop is sent. */
|
||||
readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
||||
SVC_I3C_MSTATUS_STATE_IDLE(reg), 0, 1000);
|
||||
ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
|
||||
SVC_I3C_MSTATUS_STATE_IDLE(reg),
|
||||
0, 1000);
|
||||
if (ret)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1500,6 +1511,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
|
||||
else
|
||||
svc_i3c_master_emit_force_exit(master);
|
||||
|
||||
cleanup:
|
||||
svc_i3c_master_clear_merrwarn(master);
|
||||
svc_i3c_master_flush_fifo(master);
|
||||
|
||||
@@ -1713,8 +1725,8 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
|
||||
svc_i3c_master_dequeue_xfer(master, xfer);
|
||||
mutex_unlock(&master->lock);
|
||||
|
||||
if (cmd->actual_len != xfer_len)
|
||||
ccc->dests[0].payload.len = cmd->actual_len;
|
||||
if (ccc->rnw)
|
||||
ccc->dests[0].payload.actual_len = cmd->actual_len;
|
||||
|
||||
ret = xfer->ret;
|
||||
svc_i3c_master_free_xfer(xfer);
|
||||
|
||||
@@ -13,4 +13,8 @@
|
||||
#define I2C_NO_FILTER_HIGH_FREQUENCY (1 << 5)
|
||||
#define I2C_NO_FILTER_LOW_FREQUENCY (2 << 5)
|
||||
|
||||
#define I3C_ADDR_METHOD_SETDASA (1 << 0)
|
||||
#define I3C_ADDR_METHOD_SETAASA (1 << 1)
|
||||
#define I3C_ADDR_METHOD_VENDOR (1 << 2)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <linux/i3c/device.h>
|
||||
|
||||
/* I3C CCC (Common Command Codes) related definitions */
|
||||
#define I3C_CCC_RETRIES 1
|
||||
|
||||
#define I3C_CCC_DIRECT BIT(7)
|
||||
|
||||
#define I3C_CCC_ID(id, broadcast) \
|
||||
@@ -32,6 +34,7 @@
|
||||
#define I3C_CCC_DEFSLVS I3C_CCC_ID(0x8, true)
|
||||
#define I3C_CCC_ENTTM I3C_CCC_ID(0xb, true)
|
||||
#define I3C_CCC_ENTHDR(x) I3C_CCC_ID(0x20 + (x), true)
|
||||
#define I3C_CCC_SETAASA I3C_CCC_ID(0x29, true)
|
||||
|
||||
/* Unicast-only commands */
|
||||
#define I3C_CCC_SETDASA I3C_CCC_ID(0x7, false)
|
||||
@@ -343,11 +346,15 @@ struct i3c_ccc_getxtime {
|
||||
/**
|
||||
* struct i3c_ccc_cmd_payload - CCC payload
|
||||
*
|
||||
* @len: payload length
|
||||
* @len: requested payload length
|
||||
* @actual_len: number of bytes received on a GET CCC (filled by the driver)
|
||||
* @optional_bytes: GET CCCs may return up to this many fewer bytes than @len
|
||||
* @data: payload data. This buffer must be DMA-able
|
||||
*/
|
||||
struct i3c_ccc_cmd_payload {
|
||||
u16 len;
|
||||
u16 actual_len;
|
||||
u16 optional_bytes;
|
||||
void *data;
|
||||
};
|
||||
|
||||
@@ -372,12 +379,15 @@ struct i3c_ccc_cmd_dest {
|
||||
* @ndests: number of destinations. Should always be one for broadcast commands
|
||||
* @dests: array of destinations and associated payload for this CCC. Most of
|
||||
* the time, only one destination is provided
|
||||
* @retries: number of times to retry a failed Direct GET CCC (see
|
||||
* &I3C_CCC_RETRIES)
|
||||
* @err: I3C error code
|
||||
*/
|
||||
struct i3c_ccc_cmd {
|
||||
u8 rnw;
|
||||
u8 id;
|
||||
unsigned int ndests;
|
||||
unsigned int retries;
|
||||
struct i3c_ccc_cmd_dest *dests;
|
||||
enum i3c_error_code err;
|
||||
};
|
||||
|
||||
@@ -174,10 +174,19 @@ struct i3c_device_ibi_info {
|
||||
* assigned a dynamic address by the master. Will be used during
|
||||
* bus initialization to assign it a specific dynamic address
|
||||
* before starting DAA (Dynamic Address Assignment)
|
||||
* @static_addr_method: Bitmap describing which methods of Dynamic Address
|
||||
* Assignment from a Static Address are supported by this I3C Target.
|
||||
* A value of 1 in a bit position indicates that the I3C target
|
||||
* supports that method, and a value of 0 indicates that the I3C
|
||||
* target does not support that method.
|
||||
* Bit 0: SETDASA
|
||||
* Bit 1: SETAASA
|
||||
* All other bits are reserved.
|
||||
* @pid: I3C Provisioned ID exposed by the device. This is a unique identifier
|
||||
* that may be used to attach boardinfo to i3c_dev_desc when the device
|
||||
* does not have a static address
|
||||
* @of_node: optional DT node in case the device has been described in the DT
|
||||
* @fwnode: Firmware node (DT or ACPI) in case the device has been
|
||||
* described in firmware
|
||||
*
|
||||
* This structure is used to attach board-level information to an I3C device.
|
||||
* Not all I3C devices connected on the bus will have a boardinfo. It's only
|
||||
@@ -188,8 +197,9 @@ struct i3c_dev_boardinfo {
|
||||
struct list_head node;
|
||||
u8 init_dyn_addr;
|
||||
u8 static_addr;
|
||||
u8 static_addr_method;
|
||||
u64 pid;
|
||||
struct device_node *of_node;
|
||||
struct fwnode_handle *fwnode;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -228,6 +238,8 @@ struct i3c_dev_desc {
|
||||
* every time the I3C device is rediscovered with a different dynamic
|
||||
* address assigned
|
||||
* @bus: I3C bus this device is attached to
|
||||
* @node: unregistered device list node, only for use by
|
||||
* i3c_master_register_new_i3c_devs(), it is not protected by a lock
|
||||
*
|
||||
* I3C device object exposed to I3C device drivers. The takes care of linking
|
||||
* this object to the relevant &struct_i3c_dev_desc one.
|
||||
@@ -238,6 +250,7 @@ struct i3c_device {
|
||||
struct device dev;
|
||||
struct i3c_dev_desc *desc;
|
||||
struct i3c_bus *bus;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -259,6 +272,7 @@ struct i3c_device {
|
||||
#define I3C_BUS_THIGH_MIXED_MAX_NS 41
|
||||
#define I3C_BUS_TIDLE_MIN_NS 200000
|
||||
#define I3C_BUS_TLOW_OD_MIN_NS 200
|
||||
#define I3C_BUS_THIGH_INIT_OD_MIN_NS 200
|
||||
|
||||
/**
|
||||
* enum i3c_bus_mode - I3C bus mode
|
||||
@@ -511,11 +525,17 @@ struct i3c_master_controller_ops {
|
||||
* @hotjoin: true if the master support hotjoin
|
||||
* @rpm_allowed: true if Runtime PM allowed
|
||||
* @rpm_ibi_allowed: true if IBI and Hot-Join allowed while runtime suspended
|
||||
* @ibi_wakeup: IBI can wakeup the system
|
||||
* @shutting_down: set to true when master begins shutdown or unregister
|
||||
* @boardinfo.i3c: list of I3C boardinfo objects
|
||||
* @boardinfo.i2c: list of I2C boardinfo objects
|
||||
* @boardinfo: board-level information attached to devices connected on the bus
|
||||
* @bus: I3C bus exposed by this master
|
||||
* @addr_method: Bitmap describing which methods of Address Assignment required
|
||||
* to be run for discovering all the devices on the bus.
|
||||
* Bit 0: SETDASA
|
||||
* Bit 1: SETAASA
|
||||
* All other bits are reserved.
|
||||
* @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
|
||||
@@ -545,12 +565,14 @@ struct i3c_master_controller {
|
||||
unsigned int hotjoin: 1;
|
||||
unsigned int rpm_allowed: 1;
|
||||
unsigned int rpm_ibi_allowed: 1;
|
||||
unsigned int ibi_wakeup: 1;
|
||||
bool shutting_down;
|
||||
struct {
|
||||
struct list_head i3c;
|
||||
struct list_head i2c;
|
||||
} boardinfo;
|
||||
struct i3c_bus bus;
|
||||
u8 addr_method;
|
||||
struct workqueue_struct *wq;
|
||||
struct work_struct hj_work;
|
||||
struct work_struct reg_work;
|
||||
@@ -744,6 +766,7 @@ void i3c_generic_ibi_recycle_slot(struct i3c_generic_ibi_pool *pool,
|
||||
struct i3c_ibi_slot *slot);
|
||||
|
||||
void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot);
|
||||
bool i3c_master_has_wakeup_enabled_devs(struct i3c_master_controller *master);
|
||||
|
||||
struct i3c_ibi_slot *i3c_master_get_free_ibi_slot(struct i3c_dev_desc *dev);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user