Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
# SPDX-License-Identifier: GPL-2.0-only
config ADI_I3C_MASTER
tristate "Analog Devices I3C master driver"
depends on HAS_IOMEM
help
Support for Analog Devices I3C Controller IP, an AXI-interfaced IP
core that supports I3C and I2C devices, multiple speed-grades and I3C
IBIs.
This driver can also be built as a module. If so, the module will be
called adi-i3c-master.
config CDNS_I3C_MASTER
tristate "Cadence I3C master driver"
depends on HAS_IOMEM
depends on !(ALPHA || PARISC)
help
Enable this driver if you want to support Cadence I3C master block.
config DW_I3C_MASTER
tristate "Synospsys DesignWare I3C master driver"
depends on HAS_IOMEM
depends on !(ALPHA || PARISC)
# ALPHA and PARISC needs {read,write}sl()
help
Support for Synopsys DesignWare MIPI I3C Controller.
For details please see
https://www.synopsys.com/dw/ipdir.php?ds=mipi_i3c
This driver can also be built as a module. If so, the module
will be called dw-i3c-master.
config AST2600_I3C_MASTER
tristate "ASPEED AST2600 I3C master driver"
depends on DW_I3C_MASTER
depends on ARCH_ASPEED || COMPILE_TEST
select MFD_SYSCON
help
Support for ASPEED AST2600 I3C Controller.
This hardware is an instance of the DW I3C controller; this
driver adds platform- specific support for AST2600 hardware.
This driver can also be built as a module. If so, the module
will be called ast2600-i3c-master.
config SVC_I3C_MASTER
tristate "Silvaco I3C Dual-Role Master driver"
depends on HAS_IOMEM
depends on !(ALPHA || PARISC)
help
Support for Silvaco I3C Dual-Role Master Controller.
config MIPI_I3C_HCI
tristate "MIPI I3C Host Controller Interface driver (EXPERIMENTAL)"
depends on HAS_IOMEM
help
Support for hardware following the MIPI Aliance's I3C Host Controller
Interface specification.
For details please see:
https://www.mipi.org/specifications/i3c-hci
This driver can also be built as a module. If so, the module will be
called mipi-i3c-hci.
config MIPI_I3C_HCI_PCI
tristate "MIPI I3C Host Controller Interface PCI support"
depends on MIPI_I3C_HCI
depends on PCI
select MFD_CORE
help
Support for MIPI I3C Host Controller Interface compatible hardware
on the PCI bus.
This driver can also be built as a module. If so, the module will be
called mipi-i3c-hci-pci.
config RENESAS_I3C
tristate "Renesas I3C controller driver"
depends on HAS_IOMEM
depends on ARCH_RENESAS || COMPILE_TEST
help
Support the Renesas I3C controller as found in some RZ variants.
This driver can also be built as a module. If so, the module will be
called renesas-i3c.
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_ADI_I3C_MASTER) += adi-i3c-master.o
obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o
obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Code Construct
*
* Author: Jeremy Kerr <jk@codeconstruct.com.au>
*/
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "dw-i3c-master.h"
/* AST2600-specific global register set */
#define AST2600_I3CG_REG0(idx) (((idx) * 4 * 4) + 0x10)
#define AST2600_I3CG_REG1(idx) (((idx) * 4 * 4) + 0x14)
#define AST2600_I3CG_REG0_SDA_PULLUP_EN_MASK GENMASK(29, 28)
#define AST2600_I3CG_REG0_SDA_PULLUP_EN_2K (0x0 << 28)
#define AST2600_I3CG_REG0_SDA_PULLUP_EN_750 (0x2 << 28)
#define AST2600_I3CG_REG0_SDA_PULLUP_EN_545 (0x3 << 28)
#define AST2600_I3CG_REG1_I2C_MODE BIT(0)
#define AST2600_I3CG_REG1_TEST_MODE BIT(1)
#define AST2600_I3CG_REG1_ACT_MODE_MASK GENMASK(3, 2)
#define AST2600_I3CG_REG1_ACT_MODE(x) (((x) << 2) & AST2600_I3CG_REG1_ACT_MODE_MASK)
#define AST2600_I3CG_REG1_PENDING_INT_MASK GENMASK(7, 4)
#define AST2600_I3CG_REG1_PENDING_INT(x) (((x) << 4) & AST2600_I3CG_REG1_PENDING_INT_MASK)
#define AST2600_I3CG_REG1_SA_MASK GENMASK(14, 8)
#define AST2600_I3CG_REG1_SA(x) (((x) << 8) & AST2600_I3CG_REG1_SA_MASK)
#define AST2600_I3CG_REG1_SA_EN BIT(15)
#define AST2600_I3CG_REG1_INST_ID_MASK GENMASK(19, 16)
#define AST2600_I3CG_REG1_INST_ID(x) (((x) << 16) & AST2600_I3CG_REG1_INST_ID_MASK)
#define AST2600_DEFAULT_SDA_PULLUP_OHMS 2000
#define DEV_ADDR_TABLE_IBI_PEC BIT(11)
struct ast2600_i3c {
struct dw_i3c_master dw;
struct regmap *global_regs;
unsigned int global_idx;
unsigned int sda_pullup;
};
static struct ast2600_i3c *to_ast2600_i3c(struct dw_i3c_master *dw)
{
return container_of(dw, struct ast2600_i3c, dw);
}
static int ast2600_i3c_pullup_to_reg(unsigned int ohms, u32 *regp)
{
u32 reg;
switch (ohms) {
case 2000:
reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_2K;
break;
case 750:
reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_750;
break;
case 545:
reg = AST2600_I3CG_REG0_SDA_PULLUP_EN_545;
break;
default:
return -EINVAL;
}
if (regp)
*regp = reg;
return 0;
}
static int ast2600_i3c_init(struct dw_i3c_master *dw)
{
struct ast2600_i3c *i3c = to_ast2600_i3c(dw);
u32 reg = 0;
int rc;
/* reg0: set SDA pullup values */
rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, &reg);
if (rc)
return rc;
rc = regmap_write(i3c->global_regs,
AST2600_I3CG_REG0(i3c->global_idx), reg);
if (rc)
return rc;
/* reg1: set up the instance id, but leave everything else disabled,
* as it's all for client mode
*/
reg = AST2600_I3CG_REG1_INST_ID(i3c->global_idx);
rc = regmap_write(i3c->global_regs,
AST2600_I3CG_REG1(i3c->global_idx), reg);
return rc;
}
static void ast2600_i3c_set_dat_ibi(struct dw_i3c_master *i3c,
struct i3c_dev_desc *dev,
bool enable, u32 *dat)
{
/*
* The ast2600 i3c controller will lock up on receiving 4n+1-byte IBIs
* if the PEC is disabled. We have no way to restrict the length of
* IBIs sent to the controller, so we need to unconditionally enable
* PEC checking, which means we drop a byte of payload data
*/
if (enable && dev->info.bcr & I3C_BCR_IBI_PAYLOAD) {
dev_warn_once(&i3c->base.dev,
"Enabling PEC workaround. IBI payloads will be truncated\n");
*dat |= DEV_ADDR_TABLE_IBI_PEC;
}
}
static const struct dw_i3c_platform_ops ast2600_i3c_ops = {
.init = ast2600_i3c_init,
.set_dat_ibi = ast2600_i3c_set_dat_ibi,
};
static int ast2600_i3c_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct of_phandle_args gspec;
struct ast2600_i3c *i3c;
int rc;
i3c = devm_kzalloc(&pdev->dev, sizeof(*i3c), GFP_KERNEL);
if (!i3c)
return -ENOMEM;
rc = of_parse_phandle_with_fixed_args(np, "aspeed,global-regs", 1, 0,
&gspec);
if (rc)
return -ENODEV;
i3c->global_regs = syscon_node_to_regmap(gspec.np);
of_node_put(gspec.np);
if (IS_ERR(i3c->global_regs))
return PTR_ERR(i3c->global_regs);
i3c->global_idx = gspec.args[0];
rc = of_property_read_u32(np, "sda-pullup-ohms", &i3c->sda_pullup);
if (rc)
i3c->sda_pullup = AST2600_DEFAULT_SDA_PULLUP_OHMS;
rc = ast2600_i3c_pullup_to_reg(i3c->sda_pullup, NULL);
if (rc)
dev_err(&pdev->dev, "invalid sda-pullup value %d\n",
i3c->sda_pullup);
i3c->dw.platform_ops = &ast2600_i3c_ops;
return dw_i3c_common_probe(&i3c->dw, pdev);
}
static void ast2600_i3c_remove(struct platform_device *pdev)
{
struct dw_i3c_master *dw_i3c = platform_get_drvdata(pdev);
dw_i3c_common_remove(dw_i3c);
}
static const struct of_device_id ast2600_i3c_master_of_match[] = {
{ .compatible = "aspeed,ast2600-i3c", },
{},
};
MODULE_DEVICE_TABLE(of, ast2600_i3c_master_of_match);
static struct platform_driver ast2600_i3c_driver = {
.probe = ast2600_i3c_probe,
.remove = ast2600_i3c_remove,
.driver = {
.name = "ast2600-i3c-master",
.of_match_table = ast2600_i3c_master_of_match,
},
};
module_platform_driver(ast2600_i3c_driver);
MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>");
MODULE_DESCRIPTION("ASPEED AST2600 I3C driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Code Construct
*
* Author: Jeremy Kerr <jk@codeconstruct.com.au>
*/
#include <linux/clk.h>
#include <linux/i3c/master.h>
#include <linux/reset.h>
#include <linux/types.h>
#define DW_I3C_MAX_DEVS 32
struct dw_i3c_master_caps {
u8 cmdfifodepth;
u8 datafifodepth;
};
struct dw_i3c_dat_entry {
u8 addr;
bool is_i2c_addr;
struct i3c_dev_desc *ibi_dev;
};
struct dw_i3c_master {
struct i3c_master_controller base;
struct device *dev;
u16 maxdevs;
u16 datstartaddr;
u32 free_pos;
struct {
struct list_head list;
struct dw_i3c_xfer *cur;
spinlock_t lock;
} xferqueue;
struct dw_i3c_master_caps caps;
void __iomem *regs;
struct reset_control *core_rst;
struct clk *core_clk;
struct clk *pclk;
char version[5];
char type[5];
u32 sir_rej_mask;
bool i2c_slv_prsnt;
u32 dev_addr;
u32 i3c_pp_timing;
u32 i3c_od_timing;
u32 ext_lcnt_timing;
u32 bus_free_timing;
u32 i2c_fm_timing;
u32 i2c_fmp_timing;
u32 quirks;
bool has_ibi_data;
/*
* Per-device hardware data, used to manage the device address table
* (DAT)
*
* Locking: the devs array may be referenced in IRQ context while
* processing an IBI. However, IBIs (for a specific device, which
* implies a specific DAT entry) can only happen while interrupts are
* requested for that device, which is serialised against other
* insertions/removals from the array by the global i3c infrastructure.
* So, devs_lock protects against concurrent updates to devs->ibi_dev
* between request_ibi/free_ibi and the IBI irq event.
*/
struct dw_i3c_dat_entry devs[DW_I3C_MAX_DEVS];
spinlock_t devs_lock;
/* platform-specific data */
const struct dw_i3c_platform_ops *platform_ops;
};
struct dw_i3c_platform_ops {
/*
* Called on early bus init: the i3c has been set up, but before any
* transactions have taken place. Platform implementations may use to
* perform actual device enabling with the i3c core ready.
*/
int (*init)(struct dw_i3c_master *i3c);
/*
* Initialise a DAT entry to enable/disable IBIs. Allows the platform
* to perform any device workarounds on the DAT entry before
* inserting into the hardware table.
*
* Called with the DAT lock held; must not sleep.
*/
void (*set_dat_ibi)(struct dw_i3c_master *i3c,
struct i3c_dev_desc *dev, bool enable, u32 *reg);
};
extern int dw_i3c_common_probe(struct dw_i3c_master *master,
struct platform_device *pdev);
extern void dw_i3c_common_remove(struct dw_i3c_master *master);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: BSD-3-Clause
obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci.o
mipi-i3c-hci-y := core.o ext_caps.o pio.o dma.o \
cmd_v1.o cmd_v2.o \
dat_v1.o dct_v1.o \
hci_quirks.o
obj-$(CONFIG_MIPI_I3C_HCI_PCI) += mipi-i3c-hci-pci.o
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* Common command/response related stuff
*/
#ifndef CMD_H
#define CMD_H
/*
* Those bits are common to all descriptor formats and
* may be manipulated by the core code.
*/
#define CMD_0_TOC W0_BIT_(31)
#define CMD_0_ROC W0_BIT_(30)
#define CMD_0_ATTR W0_MASK(2, 0)
#define CMD_0_TID W0_MASK(6, 3)
/*
* Response Descriptor Structure
*/
#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_ERR_FIELD GENMASK(31, 28)
enum hci_resp_err {
RESP_SUCCESS = 0x0,
RESP_ERR_CRC = 0x1,
RESP_ERR_PARITY = 0x2,
RESP_ERR_FRAME = 0x3,
RESP_ERR_ADDR_HEADER = 0x4,
RESP_ERR_BCAST_NACK_7E = 0x4,
RESP_ERR_NACK = 0x5,
RESP_ERR_OVL = 0x6,
RESP_ERR_I3C_SHORT_READ = 0x7,
RESP_ERR_HC_TERMINATED = 0x8,
RESP_ERR_I2C_WR_DATA_NACK = 0x9,
RESP_ERR_BUS_XFER_ABORTED = 0x9,
RESP_ERR_NOT_SUPPORTED = 0xa,
RESP_ERR_ABORTED_WITH_CRC = 0xb,
/* 0xc to 0xf are reserved for transfer specific errors */
};
/* TID generation (4 bits wide in all cases) */
#define hci_get_tid(bits) \
(atomic_inc_return_relaxed(&hci->next_cmd_tid) % (1U << 4))
/* This abstracts operations with our command descriptor formats */
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);
void (*prep_i2c_xfer)(struct i3c_hci *hci, struct i2c_dev_desc *dev,
struct hci_xfer *xfer);
int (*perform_daa)(struct i3c_hci *hci);
};
/* Our various instances */
extern const struct hci_cmd_ops mipi_i3c_hci_cmd_v1;
extern const struct hci_cmd_ops mipi_i3c_hci_cmd_v2;
static inline void hci_cmd_set_resp_err(u32 *response, int resp_err)
{
*response &= ~RESP_ERR_FIELD;
*response |= FIELD_PREP(RESP_ERR_FIELD, resp_err);
}
#endif
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* I3C HCI v1.0/v1.1 Command Descriptor Handling
*/
#include <linux/bitfield.h>
#include <linux/i3c/master.h>
#include "hci.h"
#include "cmd.h"
#include "dat.h"
#include "dct.h"
/*
* Address Assignment Command
*/
#define CMD_0_ATTR_A FIELD_PREP(CMD_0_ATTR, 0x2)
#define CMD_A0_TOC W0_BIT_(31)
#define CMD_A0_ROC W0_BIT_(30)
#define CMD_A0_DEV_COUNT(v) FIELD_PREP(W0_MASK(29, 26), v)
#define CMD_A0_DEV_INDEX(v) FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_A0_CMD(v) FIELD_PREP(W0_MASK(14, 7), v)
#define CMD_A0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v)
/*
* Immediate Data Transfer Command
*/
#define CMD_0_ATTR_I FIELD_PREP(CMD_0_ATTR, 0x1)
#define CMD_I1_DATA_BYTE_4(v) FIELD_PREP(W1_MASK(63, 56), v)
#define CMD_I1_DATA_BYTE_3(v) FIELD_PREP(W1_MASK(55, 48), v)
#define CMD_I1_DATA_BYTE_2(v) FIELD_PREP(W1_MASK(47, 40), v)
#define CMD_I1_DATA_BYTE_1(v) FIELD_PREP(W1_MASK(39, 32), v)
#define CMD_I1_DEF_BYTE(v) FIELD_PREP(W1_MASK(39, 32), v)
#define CMD_I0_TOC W0_BIT_(31)
#define CMD_I0_ROC W0_BIT_(30)
#define CMD_I0_RNW W0_BIT_(29)
#define CMD_I0_MODE(v) FIELD_PREP(W0_MASK(28, 26), v)
#define CMD_I0_DTT(v) FIELD_PREP(W0_MASK(25, 23), v)
#define CMD_I0_DEV_INDEX(v) FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_I0_CP W0_BIT_(15)
#define CMD_I0_CMD(v) FIELD_PREP(W0_MASK(14, 7), v)
#define CMD_I0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v)
/*
* Regular Data Transfer Command
*/
#define CMD_0_ATTR_R FIELD_PREP(CMD_0_ATTR, 0x0)
#define CMD_R1_DATA_LENGTH(v) FIELD_PREP(W1_MASK(63, 48), v)
#define CMD_R1_DEF_BYTE(v) FIELD_PREP(W1_MASK(39, 32), v)
#define CMD_R0_TOC W0_BIT_(31)
#define CMD_R0_ROC W0_BIT_(30)
#define CMD_R0_RNW W0_BIT_(29)
#define CMD_R0_MODE(v) FIELD_PREP(W0_MASK(28, 26), v)
#define CMD_R0_DBP W0_BIT_(25)
#define CMD_R0_DEV_INDEX(v) FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_R0_CP W0_BIT_(15)
#define CMD_R0_CMD(v) FIELD_PREP(W0_MASK(14, 7), v)
#define CMD_R0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v)
/*
* Combo Transfer (Write + Write/Read) Command
*/
#define CMD_0_ATTR_C FIELD_PREP(CMD_0_ATTR, 0x3)
#define CMD_C1_DATA_LENGTH(v) FIELD_PREP(W1_MASK(63, 48), v)
#define CMD_C1_OFFSET(v) FIELD_PREP(W1_MASK(47, 32), v)
#define CMD_C0_TOC W0_BIT_(31)
#define CMD_C0_ROC W0_BIT_(30)
#define CMD_C0_RNW W0_BIT_(29)
#define CMD_C0_MODE(v) FIELD_PREP(W0_MASK(28, 26), v)
#define CMD_C0_16_BIT_SUBOFFSET W0_BIT_(25)
#define CMD_C0_FIRST_PHASE_MODE W0_BIT_(24)
#define CMD_C0_DATA_LENGTH_POSITION(v) FIELD_PREP(W0_MASK(23, 22), v)
#define CMD_C0_DEV_INDEX(v) FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_C0_CP W0_BIT_(15)
#define CMD_C0_CMD(v) FIELD_PREP(W0_MASK(14, 7), v)
#define CMD_C0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v)
/*
* Internal Control Command
*/
#define CMD_0_ATTR_M FIELD_PREP(CMD_0_ATTR, 0x7)
#define CMD_M1_VENDOR_SPECIFIC W1_MASK(63, 32)
#define CMD_M0_MIPI_RESERVED W0_MASK(31, 12)
#define CMD_M0_MIPI_CMD W0_MASK(11, 8)
#define CMD_M0_VENDOR_INFO_PRESENT W0_BIT_( 7)
#define CMD_M0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v)
/* Data Transfer Speed and Mode */
enum hci_cmd_mode {
MODE_I3C_SDR0 = 0x0,
MODE_I3C_SDR1 = 0x1,
MODE_I3C_SDR2 = 0x2,
MODE_I3C_SDR3 = 0x3,
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,
MODE_I2C_UD2 = 0x3,
MODE_I2C_UD3 = 0x4,
};
static enum hci_cmd_mode get_i3c_mode(struct i3c_hci *hci)
{
struct i3c_bus *bus = i3c_master_get_bus(&hci->master);
if (bus->scl_rate.i3c > 8000000)
return MODE_I3C_SDR0;
if (bus->scl_rate.i3c > 6000000)
return MODE_I3C_SDR1;
if (bus->scl_rate.i3c > 4000000)
return MODE_I3C_SDR2;
if (bus->scl_rate.i3c > 2000000)
return MODE_I3C_SDR3;
return MODE_I3C_SDR4;
}
static enum hci_cmd_mode get_i2c_mode(struct i3c_hci *hci)
{
struct i3c_bus *bus = i3c_master_get_bus(&hci->master);
if (bus->scl_rate.i2c >= 1000000)
return MODE_I2C_FmP;
return MODE_I2C_Fm;
}
static void fill_data_bytes(struct hci_xfer *xfer, u8 *data,
unsigned int data_len)
{
xfer->cmd_desc[1] = 0;
switch (data_len) {
case 4:
xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_4(data[3]);
fallthrough;
case 3:
xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_3(data[2]);
fallthrough;
case 2:
xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_2(data[1]);
fallthrough;
case 1:
xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_1(data[0]);
fallthrough;
case 0:
break;
}
/* we consumed all the data with the cmd descriptor */
xfer->data = NULL;
}
static int hci_cmd_v1_prep_ccc(struct i3c_hci *hci,
struct hci_xfer *xfer,
u8 ccc_addr, u8 ccc_cmd, bool raw)
{
unsigned int dat_idx = 0;
enum hci_cmd_mode mode = get_i3c_mode(hci);
u8 *data = xfer->data;
unsigned int data_len = xfer->data_len;
bool rnw = xfer->rnw;
int ret;
/* this should never happen */
if (WARN_ON(raw))
return -EINVAL;
if (ccc_addr != I3C_BROADCAST_ADDR) {
ret = mipi_i3c_hci_dat_v1.get_index(hci, ccc_addr);
if (ret < 0)
return ret;
dat_idx = ret;
}
xfer->cmd_tid = hci_get_tid();
if (!rnw && data_len <= 4) {
/* we use an Immediate Data Transfer Command */
xfer->cmd_desc[0] =
CMD_0_ATTR_I |
CMD_I0_TID(xfer->cmd_tid) |
CMD_I0_CMD(ccc_cmd) | CMD_I0_CP |
CMD_I0_DEV_INDEX(dat_idx) |
CMD_I0_DTT(data_len) |
CMD_I0_MODE(mode);
fill_data_bytes(xfer, data, data_len);
} else {
/* we use a Regular Data Transfer Command */
xfer->cmd_desc[0] =
CMD_0_ATTR_R |
CMD_R0_TID(xfer->cmd_tid) |
CMD_R0_CMD(ccc_cmd) | CMD_R0_CP |
CMD_R0_DEV_INDEX(dat_idx) |
CMD_R0_MODE(mode) |
(rnw ? CMD_R0_RNW : 0);
xfer->cmd_desc[1] =
CMD_R1_DATA_LENGTH(data_len);
}
return 0;
}
static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci,
struct i3c_dev_desc *dev,
struct hci_xfer *xfer)
{
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);
u8 *data = xfer->data;
unsigned int data_len = xfer->data_len;
bool rnw = xfer->rnw;
xfer->cmd_tid = hci_get_tid();
if (!rnw && data_len <= 4) {
/* we use an Immediate Data Transfer Command */
xfer->cmd_desc[0] =
CMD_0_ATTR_I |
CMD_I0_TID(xfer->cmd_tid) |
CMD_I0_DEV_INDEX(dat_idx) |
CMD_I0_DTT(data_len) |
CMD_I0_MODE(mode);
fill_data_bytes(xfer, data, data_len);
} else {
/* we use a Regular Data Transfer Command */
xfer->cmd_desc[0] =
CMD_0_ATTR_R |
CMD_R0_TID(xfer->cmd_tid) |
CMD_R0_DEV_INDEX(dat_idx) |
CMD_R0_MODE(mode) |
(rnw ? CMD_R0_RNW : 0);
xfer->cmd_desc[1] =
CMD_R1_DATA_LENGTH(data_len);
}
}
static void hci_cmd_v1_prep_i2c_xfer(struct i3c_hci *hci,
struct i2c_dev_desc *dev,
struct hci_xfer *xfer)
{
struct i3c_hci_dev_data *dev_data = i2c_dev_get_master_data(dev);
unsigned int dat_idx = dev_data->dat_idx;
enum hci_cmd_mode mode = get_i2c_mode(hci);
u8 *data = xfer->data;
unsigned int data_len = xfer->data_len;
bool rnw = xfer->rnw;
xfer->cmd_tid = hci_get_tid();
if (!rnw && data_len <= 4) {
/* we use an Immediate Data Transfer Command */
xfer->cmd_desc[0] =
CMD_0_ATTR_I |
CMD_I0_TID(xfer->cmd_tid) |
CMD_I0_DEV_INDEX(dat_idx) |
CMD_I0_DTT(data_len) |
CMD_I0_MODE(mode);
fill_data_bytes(xfer, data, data_len);
} else {
/* we use a Regular Data Transfer Command */
xfer->cmd_desc[0] =
CMD_0_ATTR_R |
CMD_R0_TID(xfer->cmd_tid) |
CMD_R0_DEV_INDEX(dat_idx) |
CMD_R0_MODE(mode) |
(rnw ? CMD_R0_RNW : 0);
xfer->cmd_desc[1] =
CMD_R1_DATA_LENGTH(data_len);
}
}
static int hci_cmd_v1_daa(struct i3c_hci *hci)
{
struct hci_xfer *xfer;
int ret, dat_idx = -1;
u8 next_addr = 0;
u64 pid;
unsigned int dcr, bcr;
DECLARE_COMPLETION_ONSTACK(done);
xfer = hci_alloc_xfer(1);
if (!xfer)
return -ENOMEM;
/*
* Simple for now: we allocate a temporary DAT entry, do a single
* DAA, register the device which will allocate its own DAT entry
* via the core callback, then free the temporary DAT entry.
* Loop until there is no more devices to assign an address to.
* Yes, there is room for improvements.
*/
for (;;) {
ret = mipi_i3c_hci_dat_v1.alloc_entry(hci);
if (ret < 0)
break;
dat_idx = ret;
ret = i3c_master_get_free_addr(&hci->master, next_addr);
if (ret < 0)
break;
next_addr = ret;
dev_dbg(&hci->master.dev,
"next_addr = 0x%02x, DAA using DAT %d",
next_addr, dat_idx);
mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, dat_idx, next_addr);
mipi_i3c_hci_dct_index_reset(hci);
xfer->cmd_tid = hci_get_tid();
xfer->cmd_desc[0] =
CMD_0_ATTR_A |
CMD_A0_TID(xfer->cmd_tid) |
CMD_A0_CMD(I3C_CCC_ENTDAA) |
CMD_A0_DEV_INDEX(dat_idx) |
CMD_A0_DEV_COUNT(1) |
CMD_A0_ROC | CMD_A0_TOC;
xfer->cmd_desc[1] = 0;
xfer->completion = &done;
xfer->timeout = HZ;
ret = i3c_hci_process_xfer(hci, xfer, 1);
if (ret)
break;
if ((RESP_STATUS(xfer->response) == RESP_ERR_ADDR_HEADER ||
RESP_STATUS(xfer->response) == RESP_ERR_NACK) &&
RESP_DATA_LENGTH(xfer->response) == 1) {
ret = 0; /* no more devices to be assigned */
break;
}
if (RESP_STATUS(xfer->response) != RESP_SUCCESS) {
ret = -EIO;
break;
}
i3c_hci_dct_get_val(hci, 0, &pid, &dcr, &bcr);
dev_dbg(&hci->master.dev,
"assigned address %#x to device PID=0x%llx DCR=%#x BCR=%#x",
next_addr, pid, dcr, bcr);
mipi_i3c_hci_dat_v1.free_entry(hci, dat_idx);
dat_idx = -1;
/*
* TODO: Extend the subsystem layer to allow for registering
* new device and provide BCR/DCR/PID at the same time.
*/
i3c_master_add_i3c_dev_locked(&hci->master, next_addr);
}
if (dat_idx >= 0)
mipi_i3c_hci_dat_v1.free_entry(hci, dat_idx);
hci_free_xfer(xfer, 1);
return ret;
}
const struct hci_cmd_ops mipi_i3c_hci_cmd_v1 = {
.prep_ccc = hci_cmd_v1_prep_ccc,
.prep_i3c_xfer = hci_cmd_v1_prep_i3c_xfer,
.prep_i2c_xfer = hci_cmd_v1_prep_i2c_xfer,
.perform_daa = hci_cmd_v1_daa,
};
@@ -0,0 +1,311 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* I3C HCI v2.0 Command Descriptor Handling
*
* Note: The I3C HCI v2.0 spec is still in flux. The code here will change.
*/
#include <linux/bitfield.h>
#include <linux/i3c/master.h>
#include "hci.h"
#include "cmd.h"
#include "xfer_mode_rate.h"
/*
* Unified Data Transfer Command
*/
#define CMD_0_ATTR_U FIELD_PREP(CMD_0_ATTR, 0x4)
#define CMD_U3_HDR_TSP_ML_CTRL(v) FIELD_PREP(W3_MASK(107, 104), v)
#define CMD_U3_IDB4(v) FIELD_PREP(W3_MASK(103, 96), v)
#define CMD_U3_HDR_CMD(v) FIELD_PREP(W3_MASK(103, 96), v)
#define CMD_U2_IDB3(v) FIELD_PREP(W2_MASK( 95, 88), v)
#define CMD_U2_HDR_BT(v) FIELD_PREP(W2_MASK( 95, 88), v)
#define CMD_U2_IDB2(v) FIELD_PREP(W2_MASK( 87, 80), v)
#define CMD_U2_BT_CMD2(v) FIELD_PREP(W2_MASK( 87, 80), v)
#define CMD_U2_IDB1(v) FIELD_PREP(W2_MASK( 79, 72), v)
#define CMD_U2_BT_CMD1(v) FIELD_PREP(W2_MASK( 79, 72), v)
#define CMD_U2_IDB0(v) FIELD_PREP(W2_MASK( 71, 64), v)
#define CMD_U2_BT_CMD0(v) FIELD_PREP(W2_MASK( 71, 64), v)
#define CMD_U1_ERR_HANDLING(v) FIELD_PREP(W1_MASK( 63, 62), v)
#define CMD_U1_ADD_FUNC(v) FIELD_PREP(W1_MASK( 61, 56), v)
#define CMD_U1_COMBO_XFER W1_BIT_( 55)
#define CMD_U1_DATA_LENGTH(v) FIELD_PREP(W1_MASK( 53, 32), v)
#define CMD_U0_TOC W0_BIT_( 31)
#define CMD_U0_ROC W0_BIT_( 30)
#define CMD_U0_MAY_YIELD W0_BIT_( 29)
#define CMD_U0_NACK_RCNT(v) FIELD_PREP(W0_MASK( 28, 27), v)
#define CMD_U0_IDB_COUNT(v) FIELD_PREP(W0_MASK( 26, 24), v)
#define CMD_U0_MODE_INDEX(v) FIELD_PREP(W0_MASK( 22, 18), v)
#define CMD_U0_XFER_RATE(v) FIELD_PREP(W0_MASK( 17, 15), v)
#define CMD_U0_DEV_ADDRESS(v) FIELD_PREP(W0_MASK( 14, 8), v)
#define CMD_U0_RnW W0_BIT_( 7)
#define CMD_U0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v)
/*
* Address Assignment Command
*/
#define CMD_0_ATTR_A FIELD_PREP(CMD_0_ATTR, 0x2)
#define CMD_A1_DATA_LENGTH(v) FIELD_PREP(W1_MASK( 53, 32), v)
#define CMD_A0_TOC W0_BIT_( 31)
#define CMD_A0_ROC W0_BIT_( 30)
#define CMD_A0_XFER_RATE(v) FIELD_PREP(W0_MASK( 17, 15), v)
#define CMD_A0_ASSIGN_ADDRESS(v) FIELD_PREP(W0_MASK( 14, 8), v)
#define CMD_A0_TID(v) FIELD_PREP(W0_MASK( 6, 3), v)
static unsigned int get_i3c_rate_idx(struct i3c_hci *hci)
{
struct i3c_bus *bus = i3c_master_get_bus(&hci->master);
if (bus->scl_rate.i3c >= 12000000)
return XFERRATE_I3C_SDR0;
if (bus->scl_rate.i3c > 8000000)
return XFERRATE_I3C_SDR1;
if (bus->scl_rate.i3c > 6000000)
return XFERRATE_I3C_SDR2;
if (bus->scl_rate.i3c > 4000000)
return XFERRATE_I3C_SDR3;
if (bus->scl_rate.i3c > 2000000)
return XFERRATE_I3C_SDR4;
return XFERRATE_I3C_SDR_FM_FMP;
}
static unsigned int get_i2c_rate_idx(struct i3c_hci *hci)
{
struct i3c_bus *bus = i3c_master_get_bus(&hci->master);
if (bus->scl_rate.i2c >= 1000000)
return XFERRATE_I2C_FMP;
return XFERRATE_I2C_FM;
}
static void hci_cmd_v2_prep_private_xfer(struct i3c_hci *hci,
struct hci_xfer *xfer,
u8 addr, unsigned int mode,
unsigned int rate)
{
u8 *data = xfer->data;
unsigned int data_len = xfer->data_len;
bool rnw = xfer->rnw;
xfer->cmd_tid = hci_get_tid();
if (!rnw && data_len <= 5) {
xfer->cmd_desc[0] =
CMD_0_ATTR_U |
CMD_U0_TID(xfer->cmd_tid) |
CMD_U0_DEV_ADDRESS(addr) |
CMD_U0_XFER_RATE(rate) |
CMD_U0_MODE_INDEX(mode) |
CMD_U0_IDB_COUNT(data_len);
xfer->cmd_desc[1] =
CMD_U1_DATA_LENGTH(0);
xfer->cmd_desc[2] = 0;
xfer->cmd_desc[3] = 0;
switch (data_len) {
case 5:
xfer->cmd_desc[3] |= CMD_U3_IDB4(data[4]);
fallthrough;
case 4:
xfer->cmd_desc[2] |= CMD_U2_IDB3(data[3]);
fallthrough;
case 3:
xfer->cmd_desc[2] |= CMD_U2_IDB2(data[2]);
fallthrough;
case 2:
xfer->cmd_desc[2] |= CMD_U2_IDB1(data[1]);
fallthrough;
case 1:
xfer->cmd_desc[2] |= CMD_U2_IDB0(data[0]);
fallthrough;
case 0:
break;
}
/* we consumed all the data with the cmd descriptor */
xfer->data = NULL;
} else {
xfer->cmd_desc[0] =
CMD_0_ATTR_U |
CMD_U0_TID(xfer->cmd_tid) |
(rnw ? CMD_U0_RnW : 0) |
CMD_U0_DEV_ADDRESS(addr) |
CMD_U0_XFER_RATE(rate) |
CMD_U0_MODE_INDEX(mode);
xfer->cmd_desc[1] =
CMD_U1_DATA_LENGTH(data_len);
xfer->cmd_desc[2] = 0;
xfer->cmd_desc[3] = 0;
}
}
static int hci_cmd_v2_prep_ccc(struct i3c_hci *hci, struct hci_xfer *xfer,
u8 ccc_addr, u8 ccc_cmd, bool raw)
{
unsigned int mode = XFERMODE_IDX_I3C_SDR;
unsigned int rate = get_i3c_rate_idx(hci);
u8 *data = xfer->data;
unsigned int data_len = xfer->data_len;
bool rnw = xfer->rnw;
if (raw && ccc_addr != I3C_BROADCAST_ADDR) {
hci_cmd_v2_prep_private_xfer(hci, xfer, ccc_addr, mode, rate);
return 0;
}
xfer->cmd_tid = hci_get_tid();
if (!rnw && data_len <= 4) {
xfer->cmd_desc[0] =
CMD_0_ATTR_U |
CMD_U0_TID(xfer->cmd_tid) |
CMD_U0_DEV_ADDRESS(ccc_addr) |
CMD_U0_XFER_RATE(rate) |
CMD_U0_MODE_INDEX(mode) |
CMD_U0_IDB_COUNT(data_len + (!raw ? 0 : 1));
xfer->cmd_desc[1] =
CMD_U1_DATA_LENGTH(0);
xfer->cmd_desc[2] =
CMD_U2_IDB0(ccc_cmd);
xfer->cmd_desc[3] = 0;
switch (data_len) {
case 4:
xfer->cmd_desc[3] |= CMD_U3_IDB4(data[3]);
fallthrough;
case 3:
xfer->cmd_desc[2] |= CMD_U2_IDB3(data[2]);
fallthrough;
case 2:
xfer->cmd_desc[2] |= CMD_U2_IDB2(data[1]);
fallthrough;
case 1:
xfer->cmd_desc[2] |= CMD_U2_IDB1(data[0]);
fallthrough;
case 0:
break;
}
/* we consumed all the data with the cmd descriptor */
xfer->data = NULL;
} else {
xfer->cmd_desc[0] =
CMD_0_ATTR_U |
CMD_U0_TID(xfer->cmd_tid) |
(rnw ? CMD_U0_RnW : 0) |
CMD_U0_DEV_ADDRESS(ccc_addr) |
CMD_U0_XFER_RATE(rate) |
CMD_U0_MODE_INDEX(mode) |
CMD_U0_IDB_COUNT(!raw ? 0 : 1);
xfer->cmd_desc[1] =
CMD_U1_DATA_LENGTH(data_len);
xfer->cmd_desc[2] =
CMD_U2_IDB0(ccc_cmd);
xfer->cmd_desc[3] = 0;
}
return 0;
}
static void hci_cmd_v2_prep_i3c_xfer(struct i3c_hci *hci,
struct i3c_dev_desc *dev,
struct hci_xfer *xfer)
{
unsigned int mode = XFERMODE_IDX_I3C_SDR;
unsigned int rate = get_i3c_rate_idx(hci);
u8 addr = dev->info.dyn_addr;
hci_cmd_v2_prep_private_xfer(hci, xfer, addr, mode, rate);
}
static void hci_cmd_v2_prep_i2c_xfer(struct i3c_hci *hci,
struct i2c_dev_desc *dev,
struct hci_xfer *xfer)
{
unsigned int mode = XFERMODE_IDX_I2C;
unsigned int rate = get_i2c_rate_idx(hci);
u8 addr = dev->addr;
hci_cmd_v2_prep_private_xfer(hci, xfer, addr, mode, rate);
}
static int hci_cmd_v2_daa(struct i3c_hci *hci)
{
struct hci_xfer *xfer;
int ret;
u8 next_addr = 0;
u32 device_id[2];
u64 pid;
unsigned int dcr, bcr;
DECLARE_COMPLETION_ONSTACK(done);
xfer = hci_alloc_xfer(2);
if (!xfer)
return -ENOMEM;
xfer[0].data = &device_id;
xfer[0].data_len = 8;
xfer[0].rnw = true;
xfer[0].cmd_desc[1] = CMD_A1_DATA_LENGTH(8);
xfer[1].completion = &done;
xfer[1].timeout = HZ;
for (;;) {
ret = i3c_master_get_free_addr(&hci->master, next_addr);
if (ret < 0)
break;
next_addr = ret;
dev_dbg(&hci->master.dev, "next_addr = 0x%02x", next_addr);
xfer[0].cmd_tid = hci_get_tid();
xfer[0].cmd_desc[0] =
CMD_0_ATTR_A |
CMD_A0_TID(xfer[0].cmd_tid) |
CMD_A0_ROC;
xfer[1].cmd_tid = hci_get_tid();
xfer[1].cmd_desc[0] =
CMD_0_ATTR_A |
CMD_A0_TID(xfer[1].cmd_tid) |
CMD_A0_ASSIGN_ADDRESS(next_addr) |
CMD_A0_ROC |
CMD_A0_TOC;
ret = i3c_hci_process_xfer(hci, xfer, 2);
if (ret)
break;
if (RESP_STATUS(xfer[0].response) != RESP_SUCCESS) {
ret = 0; /* no more devices to be assigned */
break;
}
if (RESP_STATUS(xfer[1].response) != RESP_SUCCESS) {
ret = -EIO;
break;
}
pid = FIELD_GET(W1_MASK(47, 32), device_id[1]);
pid = (pid << 32) | device_id[0];
bcr = FIELD_GET(W1_MASK(55, 48), device_id[1]);
dcr = FIELD_GET(W1_MASK(63, 56), device_id[1]);
dev_dbg(&hci->master.dev,
"assigned address %#x to device PID=0x%llx DCR=%#x BCR=%#x",
next_addr, pid, dcr, bcr);
/*
* TODO: Extend the subsystem layer to allow for registering
* new device and provide BCR/DCR/PID at the same time.
*/
i3c_master_add_i3c_dev_locked(&hci->master, next_addr);
}
hci_free_xfer(xfer, 2);
return ret;
}
const struct hci_cmd_ops mipi_i3c_hci_cmd_v2 = {
.prep_ccc = hci_cmd_v2_prep_ccc,
.prep_i3c_xfer = hci_cmd_v2_prep_i3c_xfer,
.prep_i2c_xfer = hci_cmd_v2_prep_i2c_xfer,
.perform_daa = hci_cmd_v2_daa,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* Common DAT related stuff
*/
#ifndef DAT_H
#define DAT_H
/* Global DAT flags */
#define DAT_0_I2C_DEVICE W0_BIT_(31)
#define DAT_0_SIR_REJECT W0_BIT_(13)
#define DAT_0_IBI_PAYLOAD W0_BIT_(12)
struct hci_dat_ops {
int (*init)(struct i3c_hci *hci);
int (*alloc_entry)(struct i3c_hci *hci);
void (*free_entry)(struct i3c_hci *hci, unsigned int dat_idx);
void (*set_dynamic_addr)(struct i3c_hci *hci, unsigned int dat_idx, u8 addr);
void (*set_static_addr)(struct i3c_hci *hci, unsigned int dat_idx, u8 addr);
void (*set_flags)(struct i3c_hci *hci, unsigned int dat_idx, u32 w0, u32 w1);
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);
};
extern const struct hci_dat_ops mipi_i3c_hci_dat_v1;
#endif
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*/
#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/i3c/master.h>
#include <linux/io.h>
#include "hci.h"
#include "dat.h"
/*
* Device Address Table Structure
*/
#define DAT_1_AUTOCMD_HDR_CODE W1_MASK(58, 51)
#define DAT_1_AUTOCMD_MODE W1_MASK(50, 48)
#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)
#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)
#define DAT_0_TS W0_BIT_(15)
#define DAT_0_MR_REJECT W0_BIT_(14)
/* DAT_0_SIR_REJECT W0_BIT_(13) */
/* DAT_0_IBI_PAYLOAD W0_BIT_(12) */
#define DAT_0_STATIC_ADDRESS W0_MASK(6, 0)
#define dat_w0_read(i) hci->DAT[i].w0
#define dat_w1_read(i) hci->DAT[i].w1
#define dat_w0_write(i, v) hci_dat_w0_write(hci, i, v)
#define dat_w1_write(i, v) hci_dat_w1_write(hci, i, v)
static inline void hci_dat_w0_write(struct i3c_hci *hci, int i, u32 v)
{
hci->DAT[i].w0 = v;
writel(v, hci->DAT_regs + i * 8);
}
static inline void hci_dat_w1_write(struct i3c_hci *hci, int i, u32 v)
{
hci->DAT[i].w1 = v;
writel(v, hci->DAT_regs + i * 8 + 4);
}
static int hci_dat_v1_init(struct i3c_hci *hci)
{
struct device *dev = hci->master.dev.parent;
unsigned int dat_idx;
if (!hci->DAT_regs) {
dev_err(&hci->master.dev,
"only DAT in register space is supported at the moment\n");
return -EOPNOTSUPP;
}
if (hci->DAT_entry_size != 8) {
dev_err(&hci->master.dev,
"only 8-bytes DAT entries are supported at the moment\n");
return -EOPNOTSUPP;
}
if (!hci->DAT) {
hci->DAT = devm_kcalloc(dev, hci->DAT_entries, hci->DAT_entry_size, GFP_KERNEL);
if (!hci->DAT)
return -ENOMEM;
}
if (!hci->DAT_data) {
/* use a bitmap for faster free slot search */
hci->DAT_data = devm_bitmap_zalloc(dev, hci->DAT_entries, GFP_KERNEL);
if (!hci->DAT_data)
return -ENOMEM;
/* clear them */
for (dat_idx = 0; dat_idx < hci->DAT_entries; dat_idx++) {
dat_w0_write(dat_idx, 0);
dat_w1_write(dat_idx, 0);
}
}
return 0;
}
static int hci_dat_v1_alloc_entry(struct i3c_hci *hci)
{
unsigned int dat_idx;
int ret;
if (!hci->DAT_data) {
ret = hci_dat_v1_init(hci);
if (ret)
return ret;
}
dat_idx = find_first_zero_bit(hci->DAT_data, hci->DAT_entries);
if (dat_idx >= hci->DAT_entries)
return -ENOENT;
__set_bit(dat_idx, hci->DAT_data);
/* default flags */
dat_w0_write(dat_idx, DAT_0_SIR_REJECT | DAT_0_MR_REJECT);
return dat_idx;
}
static void hci_dat_v1_free_entry(struct i3c_hci *hci, unsigned int dat_idx)
{
dat_w0_write(dat_idx, 0);
dat_w1_write(dat_idx, 0);
if (hci->DAT_data)
__clear_bit(dat_idx, hci->DAT_data);
}
static void hci_dat_v1_set_dynamic_addr(struct i3c_hci *hci,
unsigned int dat_idx, u8 address)
{
u32 dat_w0;
dat_w0 = dat_w0_read(dat_idx);
dat_w0 &= ~(DAT_0_DYNAMIC_ADDRESS | DAT_0_DYNADDR_PARITY);
dat_w0 |= FIELD_PREP(DAT_0_DYNAMIC_ADDRESS, address) |
(parity8(address) ? 0 : DAT_0_DYNADDR_PARITY);
dat_w0_write(dat_idx, dat_w0);
}
static void hci_dat_v1_set_static_addr(struct i3c_hci *hci,
unsigned int dat_idx, u8 address)
{
u32 dat_w0;
dat_w0 = dat_w0_read(dat_idx);
dat_w0 &= ~DAT_0_STATIC_ADDRESS;
dat_w0 |= FIELD_PREP(DAT_0_STATIC_ADDRESS, address);
dat_w0_write(dat_idx, dat_w0);
}
static void hci_dat_v1_set_flags(struct i3c_hci *hci, unsigned int dat_idx,
u32 w0_flags, u32 w1_flags)
{
u32 dat_w0, dat_w1;
dat_w0 = dat_w0_read(dat_idx);
dat_w1 = dat_w1_read(dat_idx);
dat_w0 |= w0_flags;
dat_w1 |= w1_flags;
dat_w0_write(dat_idx, dat_w0);
dat_w1_write(dat_idx, dat_w1);
}
static void hci_dat_v1_clear_flags(struct i3c_hci *hci, unsigned int dat_idx,
u32 w0_flags, u32 w1_flags)
{
u32 dat_w0, dat_w1;
dat_w0 = dat_w0_read(dat_idx);
dat_w1 = dat_w1_read(dat_idx);
dat_w0 &= ~w0_flags;
dat_w1 &= ~w1_flags;
dat_w0_write(dat_idx, dat_w0);
dat_w1_write(dat_idx, dat_w1);
}
static int hci_dat_v1_get_index(struct i3c_hci *hci, u8 dev_addr)
{
unsigned int dat_idx;
u32 dat_w0;
for_each_set_bit(dat_idx, hci->DAT_data, hci->DAT_entries) {
dat_w0 = dat_w0_read(dat_idx);
if (FIELD_GET(DAT_0_DYNAMIC_ADDRESS, dat_w0) == dev_addr)
return dat_idx;
}
return -ENODEV;
}
static void hci_dat_v1_restore(struct i3c_hci *hci)
{
for (int i = 0; i < hci->DAT_entries; i++) {
writel(hci->DAT[i].w0, hci->DAT_regs + i * 8);
writel(hci->DAT[i].w1, hci->DAT_regs + i * 8 + 4);
}
}
const struct hci_dat_ops mipi_i3c_hci_dat_v1 = {
.init = hci_dat_v1_init,
.alloc_entry = hci_dat_v1_alloc_entry,
.free_entry = hci_dat_v1_free_entry,
.set_dynamic_addr = hci_dat_v1_set_dynamic_addr,
.set_static_addr = hci_dat_v1_set_static_addr,
.set_flags = hci_dat_v1_set_flags,
.clear_flags = hci_dat_v1_clear_flags,
.get_index = hci_dat_v1_get_index,
.restore = hci_dat_v1_restore,
};
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* Common DCT related stuff
*/
#ifndef DCT_H
#define DCT_H
void i3c_hci_dct_get_val(struct i3c_hci *hci, unsigned int dct_idx,
u64 *pid, unsigned int *dcr, unsigned int *bcr);
#endif
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*/
#include <linux/device.h>
#include <linux/bitfield.h>
#include <linux/i3c/master.h>
#include <linux/io.h>
#include "hci.h"
#include "dct.h"
/*
* Device Characteristic Table
*/
void i3c_hci_dct_get_val(struct i3c_hci *hci, unsigned int dct_idx,
u64 *pid, unsigned int *dcr, unsigned int *bcr)
{
void __iomem *reg = hci->DCT_regs + dct_idx * 4 * 4;
u32 dct_entry_data[4];
unsigned int i;
for (i = 0; i < 4; i++) {
dct_entry_data[i] = readl(reg);
reg += 4;
}
*pid = ((u64)dct_entry_data[0]) << (47 - 32 + 1) |
FIELD_GET(W1_MASK(47, 32), dct_entry_data[1]);
*dcr = FIELD_GET(W2_MASK(71, 64), dct_entry_data[2]);
*bcr = FIELD_GET(W2_MASK(79, 72), dct_entry_data[2]);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*/
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/i3c/master.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include "hci.h"
#include "ext_caps.h"
#include "xfer_mode_rate.h"
/* Extended Capability Header */
#define CAP_HEADER_LENGTH GENMASK(23, 8)
#define CAP_HEADER_ID GENMASK(7, 0)
static int hci_extcap_hardware_id(struct i3c_hci *hci, void __iomem *base)
{
hci->vendor_mipi_id = readl(base + 0x04);
hci->vendor_version_id = readl(base + 0x08);
hci->vendor_product_id = readl(base + 0x0c);
dev_dbg(&hci->master.dev, "vendor MIPI ID: %#x\n", hci->vendor_mipi_id);
dev_dbg(&hci->master.dev, "vendor version ID: %#x\n", hci->vendor_version_id);
dev_dbg(&hci->master.dev, "vendor product ID: %#x\n", hci->vendor_product_id);
/* ought to go in a table if this grows too much */
switch (hci->vendor_mipi_id) {
case MIPI_VENDOR_NXP:
hci->quirks |= HCI_QUIRK_RAW_CCC;
dev_dbg(&hci->master.dev, "raw CCC quirks set");
break;
}
return 0;
}
static int hci_extcap_master_config(struct i3c_hci *hci, void __iomem *base)
{
u32 master_config = readl(base + 0x04);
unsigned int operation_mode = FIELD_GET(GENMASK(5, 4), master_config);
static const char * const functionality[] = {
"(unknown)", "master only", "target only",
"primary/secondary master" };
dev_dbg(&hci->master.dev, "operation mode: %s\n", functionality[operation_mode]);
if (operation_mode & 0x1)
return 0;
dev_err(&hci->master.dev, "only master mode is currently supported\n");
return -EOPNOTSUPP;
}
static int hci_extcap_multi_bus(struct i3c_hci *hci, void __iomem *base)
{
u32 bus_instance = readl(base + 0x04);
unsigned int count = FIELD_GET(GENMASK(3, 0), bus_instance);
dev_dbg(&hci->master.dev, "%d bus instances\n", count);
return 0;
}
static int hci_extcap_xfer_modes(struct i3c_hci *hci, void __iomem *base)
{
u32 header = readl(base);
u32 entries = FIELD_GET(CAP_HEADER_LENGTH, header) - 1;
unsigned int index;
dev_dbg(&hci->master.dev, "transfer mode table has %d entries\n", entries);
base += 4; /* skip header */
for (index = 0; index < entries; index++) {
u32 mode_entry = readl(base);
dev_dbg(&hci->master.dev, "mode %d: 0x%08x",
index, mode_entry);
/* TODO: will be needed when I3C core does more than SDR */
base += 4;
}
return 0;
}
static int hci_extcap_xfer_rates(struct i3c_hci *hci, void __iomem *base)
{
u32 header = readl(base);
u32 entries = FIELD_GET(CAP_HEADER_LENGTH, header) - 1;
u32 rate_entry;
unsigned int index, rate, rate_id, mode_id;
base += 4; /* skip header */
dev_dbg(&hci->master.dev, "available data rates:\n");
for (index = 0; index < entries; index++) {
rate_entry = readl(base);
dev_dbg(&hci->master.dev, "entry %d: 0x%08x",
index, rate_entry);
rate = FIELD_GET(XFERRATE_ACTUAL_RATE_KHZ, rate_entry);
rate_id = FIELD_GET(XFERRATE_RATE_ID, rate_entry);
mode_id = FIELD_GET(XFERRATE_MODE_ID, rate_entry);
dev_dbg(&hci->master.dev, "rate %d for %s = %d kHz\n",
rate_id,
mode_id == XFERRATE_MODE_I3C ? "I3C" :
mode_id == XFERRATE_MODE_I2C ? "I2C" :
"unknown mode",
rate);
base += 4;
}
return 0;
}
static int hci_extcap_auto_command(struct i3c_hci *hci, void __iomem *base)
{
u32 autocmd_ext_caps = readl(base + 0x04);
unsigned int max_count = FIELD_GET(GENMASK(3, 0), autocmd_ext_caps);
u32 autocmd_ext_config = readl(base + 0x08);
unsigned int count = FIELD_GET(GENMASK(3, 0), autocmd_ext_config);
dev_dbg(&hci->master.dev, "%d/%d active auto-command entries\n",
count, max_count);
/* remember auto-command register location for later use */
hci->AUTOCMD_regs = base;
return 0;
}
static int hci_extcap_debug(struct i3c_hci *hci, void __iomem *base)
{
dev_dbg(&hci->master.dev, "debug registers present\n");
hci->DEBUG_regs = base;
return 0;
}
static int hci_extcap_scheduled_cmd(struct i3c_hci *hci, void __iomem *base)
{
dev_dbg(&hci->master.dev, "scheduled commands available\n");
/* hci->schedcmd_regs = base; */
return 0;
}
static int hci_extcap_non_curr_master(struct i3c_hci *hci, void __iomem *base)
{
dev_dbg(&hci->master.dev, "Non-Current Master support available\n");
/* hci->NCM_regs = base; */
return 0;
}
static int hci_extcap_ccc_resp_conf(struct i3c_hci *hci, void __iomem *base)
{
dev_dbg(&hci->master.dev, "CCC Response Configuration available\n");
return 0;
}
static int hci_extcap_global_DAT(struct i3c_hci *hci, void __iomem *base)
{
dev_dbg(&hci->master.dev, "Global DAT available\n");
return 0;
}
static int hci_extcap_multilane(struct i3c_hci *hci, void __iomem *base)
{
dev_dbg(&hci->master.dev, "Master Multi-Lane support available\n");
return 0;
}
static int hci_extcap_ncm_multilane(struct i3c_hci *hci, void __iomem *base)
{
dev_dbg(&hci->master.dev, "NCM Multi-Lane support available\n");
return 0;
}
struct hci_ext_caps {
u8 id;
u16 min_length;
int (*parser)(struct i3c_hci *hci, void __iomem *base);
};
#define EXT_CAP(_id, _highest_mandatory_reg_offset, _parser) \
{ .id = (_id), .parser = (_parser), \
.min_length = (_highest_mandatory_reg_offset)/4 + 1 }
static const struct hci_ext_caps ext_capabilities[] = {
EXT_CAP(0x01, 0x0c, hci_extcap_hardware_id),
EXT_CAP(0x02, 0x04, hci_extcap_master_config),
EXT_CAP(0x03, 0x04, hci_extcap_multi_bus),
EXT_CAP(0x04, 0x24, hci_extcap_xfer_modes),
EXT_CAP(0x05, 0x08, hci_extcap_auto_command),
EXT_CAP(0x08, 0x40, hci_extcap_xfer_rates),
EXT_CAP(0x0c, 0x10, hci_extcap_debug),
EXT_CAP(0x0d, 0x0c, hci_extcap_scheduled_cmd),
EXT_CAP(0x0e, 0x80, hci_extcap_non_curr_master), /* TODO confirm size */
EXT_CAP(0x0f, 0x04, hci_extcap_ccc_resp_conf),
EXT_CAP(0x10, 0x08, hci_extcap_global_DAT),
EXT_CAP(0x9d, 0x04, hci_extcap_multilane),
EXT_CAP(0x9e, 0x04, hci_extcap_ncm_multilane),
};
static int hci_extcap_vendor_NXP(struct i3c_hci *hci, void __iomem *base)
{
hci->vendor_data = (__force void *)base;
dev_dbg(&hci->master.dev, "Build Date Info = %#x\n", readl(base + 1 * 4));
/* reset the FPGA */
writel(0xdeadbeef, base + 1*4);
return 0;
}
struct hci_ext_cap_vendor_specific {
u32 vendor;
u8 cap;
u16 min_length;
int (*parser)(struct i3c_hci *hci, void __iomem *base);
};
#define EXT_CAP_VENDOR(_vendor, _cap, _highest_mandatory_reg_offset) \
{ .vendor = (MIPI_VENDOR_##_vendor), .cap = (_cap), \
.parser = (hci_extcap_vendor_##_vendor), \
.min_length = (_highest_mandatory_reg_offset)/4 + 1 }
static const struct hci_ext_cap_vendor_specific vendor_ext_caps[] = {
EXT_CAP_VENDOR(NXP, 0xc0, 0x20),
};
static int hci_extcap_vendor_specific(struct i3c_hci *hci, void __iomem *base,
u32 cap_id, u32 cap_length)
{
const struct hci_ext_cap_vendor_specific *vendor_cap_entry;
int i;
vendor_cap_entry = NULL;
for (i = 0; i < ARRAY_SIZE(vendor_ext_caps); i++) {
if (vendor_ext_caps[i].vendor == hci->vendor_mipi_id &&
vendor_ext_caps[i].cap == cap_id) {
vendor_cap_entry = &vendor_ext_caps[i];
break;
}
}
if (!vendor_cap_entry) {
dev_dbg(&hci->master.dev, "unknown ext_cap 0x%02x for vendor 0x%02x\n",
cap_id, hci->vendor_mipi_id);
return 0;
}
if (cap_length < vendor_cap_entry->min_length) {
dev_err(&hci->master.dev,
"ext_cap 0x%02x has size %d (expecting >= %d)\n",
cap_id, cap_length, vendor_cap_entry->min_length);
return -EINVAL;
}
return vendor_cap_entry->parser(hci, base);
}
int i3c_hci_parse_ext_caps(struct i3c_hci *hci)
{
void __iomem *curr_cap = hci->EXTCAPS_regs;
void __iomem *end = curr_cap + 0x1000; /* some arbitrary limit */
u32 cap_header, cap_id, cap_length;
const struct hci_ext_caps *cap_entry;
int i, err = 0;
if (!curr_cap)
return 0;
for (; !err && curr_cap < end; curr_cap += cap_length * 4) {
cap_header = readl(curr_cap);
cap_id = FIELD_GET(CAP_HEADER_ID, cap_header);
cap_length = FIELD_GET(CAP_HEADER_LENGTH, cap_header);
dev_dbg(&hci->master.dev, "id=0x%02x length=%d",
cap_id, cap_length);
if (!cap_id || !cap_length)
break;
if (curr_cap + cap_length * 4 >= end) {
dev_err(&hci->master.dev,
"ext_cap 0x%02x has size %d (too big)\n",
cap_id, cap_length);
err = -EINVAL;
break;
}
if (cap_id >= 0xc0 && cap_id <= 0xcf) {
err = hci_extcap_vendor_specific(hci, curr_cap,
cap_id, cap_length);
continue;
}
cap_entry = NULL;
for (i = 0; i < ARRAY_SIZE(ext_capabilities); i++) {
if (ext_capabilities[i].id == cap_id) {
cap_entry = &ext_capabilities[i];
break;
}
}
if (!cap_entry) {
dev_dbg(&hci->master.dev, "unknown ext_cap 0x%02x\n", cap_id);
} else if (cap_length < cap_entry->min_length) {
dev_err(&hci->master.dev,
"ext_cap 0x%02x has size %d (expecting >= %d)\n",
cap_id, cap_length, cap_entry->min_length);
err = -EINVAL;
} else {
err = cap_entry->parser(hci, curr_cap);
}
}
return err;
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* Extended Capability Definitions
*/
#ifndef EXTCAPS_H
#define EXTCAPS_H
/* MIPI vendor IDs */
#define MIPI_VENDOR_NXP 0x11b
int i3c_hci_parse_ext_caps(struct i3c_hci *hci);
#endif
@@ -0,0 +1,194 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* Common HCI stuff
*/
#ifndef HCI_H
#define HCI_H
#include <linux/io.h>
#include <linux/jiffies.h>
/* 32-bit word aware bit and mask macros */
#define W0_MASK(h, l) GENMASK((h) - 0, (l) - 0)
#define W1_MASK(h, l) GENMASK((h) - 32, (l) - 32)
#define W2_MASK(h, l) GENMASK((h) - 64, (l) - 64)
#define W3_MASK(h, l) GENMASK((h) - 96, (l) - 96)
/* Same for single bit macros (trailing _ to align with W*_MASK width) */
#define W0_BIT_(x) BIT((x) - 0)
#define W1_BIT_(x) BIT((x) - 32)
#define W2_BIT_(x) BIT((x) - 64)
#define W3_BIT_(x) BIT((x) - 96)
#define reg_read(r) readl(hci->base_regs + (r))
#define reg_write(r, v) writel(v, hci->base_regs + (r))
#define reg_set(r, v) reg_write(r, reg_read(r) | (v))
#define reg_clear(r, v) reg_write(r, reg_read(r) & ~(v))
struct hci_cmd_ops;
struct dat_words {
u32 w0;
u32 w1;
};
/* Our main structure */
struct i3c_hci {
struct i3c_master_controller master;
void __iomem *base_regs;
void __iomem *DAT_regs;
void __iomem *DCT_regs;
void __iomem *RHS_regs;
void __iomem *PIO_regs;
void __iomem *EXTCAPS_regs;
void __iomem *AUTOCMD_regs;
void __iomem *DEBUG_regs;
const struct hci_io_ops *io;
void *io_data;
const struct hci_cmd_ops *cmd;
spinlock_t lock;
struct mutex control_mutex;
atomic_t next_cmd_tid;
bool irq_inactive;
bool enqueue_blocked;
bool recovery_needed;
bool hj_init_done;
wait_queue_head_t enqueue_wait_queue;
u32 caps;
unsigned int quirks;
unsigned int DAT_entries;
unsigned int DAT_entry_size;
void *DAT_data;
struct dat_words *DAT;
struct i3c_dev_desc **ibi_devs;
unsigned int DCT_entries;
unsigned int DCT_entry_size;
u8 version_major;
u8 version_minor;
u8 revision;
u8 dyn_addr;
u32 vendor_mipi_id;
u32 vendor_version_id;
u32 vendor_product_id;
void *vendor_data;
};
/*
* 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 completion field needs to be initialized before queueing with
* hci->io->queue_xfer(), and requires CMD_0_ROC to be set.
*/
struct hci_xfer {
u32 cmd_desc[4];
u32 response;
bool rnw;
bool started;
void *data;
unsigned int data_len;
unsigned int cmd_tid;
struct completion *completion;
unsigned long timeout;
unsigned long start_jiffies;
union {
struct {
/* PIO specific */
struct hci_xfer *next_xfer;
struct hci_xfer *next_data;
struct hci_xfer *next_resp;
unsigned int data_left;
u32 data_word_before_partial;
};
struct {
/* DMA specific */
struct i3c_dma *dma;
struct hci_xfer *final_xfer;
int ring_number;
int ring_entry;
int xfer_list_pos;
};
};
};
static inline struct hci_xfer *hci_alloc_xfer(unsigned int n)
{
return kzalloc_objs(struct hci_xfer, n);
}
static inline void hci_free_xfer(struct hci_xfer *xfer, unsigned int n)
{
kfree(xfer);
}
static inline void hci_start_xfer(struct hci_xfer *xfer)
{
if (!xfer->started) {
xfer->started = true;
xfer->start_jiffies = jiffies;
}
}
/* This abstracts PIO vs DMA operations */
struct hci_io_ops {
bool (*irq_handler)(struct i3c_hci *hci);
int (*queue_xfer)(struct i3c_hci *hci, struct hci_xfer *xfer, int n);
bool (*dequeue_xfer)(struct i3c_hci *hci, struct hci_xfer *xfer, int n);
int (*handle_error)(struct i3c_hci *hci, struct hci_xfer *xfer, int n);
int (*request_ibi)(struct i3c_hci *hci, struct i3c_dev_desc *dev,
const struct i3c_ibi_setup *req);
void (*free_ibi)(struct i3c_hci *hci, struct i3c_dev_desc *dev);
void (*recycle_ibi_slot)(struct i3c_hci *hci, struct i3c_dev_desc *dev,
struct i3c_ibi_slot *slot);
int (*init)(struct i3c_hci *hci);
void (*cleanup)(struct i3c_hci *hci);
void (*suspend)(struct i3c_hci *hci);
void (*resume)(struct i3c_hci *hci);
};
extern const struct hci_io_ops mipi_i3c_hci_pio;
extern const struct hci_io_ops mipi_i3c_hci_dma;
/* Our per device master private data */
struct i3c_hci_dev_data {
int dat_idx;
void *ibi_data;
};
/* list of quirks */
#define HCI_QUIRK_RAW_CCC BIT(1) /* CCC framing must be explicit */
#define HCI_QUIRK_PIO_MODE BIT(2) /* Set PIO mode for AMD platforms */
#define HCI_QUIRK_OD_PP_TIMING BIT(3) /* Set OD and PP timings for AMD platforms */
#define HCI_QUIRK_RESP_BUF_THLD BIT(4) /* Set resp buf thld to 0 for AMD platforms */
#define HCI_QUIRK_RPM_ALLOWED BIT(5) /* Runtime PM allowed */
#define HCI_QUIRK_RPM_IBI_ALLOWED BIT(6) /* IBI and Hot-Join allowed while runtime suspended */
#define HCI_QUIRK_RPM_PARENT_MANAGED BIT(7) /* Runtime PM managed by parent device */
#define HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET BIT(8) /* Do PIO queue SW resets after DMA abort */
#define HCI_QUIRK_DMA_REQUIRES_HC_ABORT BIT(9) /* Use HC_CONTROL ABORT to abort DMA */
/* global functions */
void mipi_i3c_hci_resume(struct i3c_hci *hci);
void mipi_i3c_hci_abort(struct i3c_hci *hci);
void mipi_i3c_hci_pio_reset(struct i3c_hci *hci);
void mipi_i3c_hci_pio_reset_all_queues(struct i3c_hci *hci);
void mipi_i3c_hci_dct_index_reset(struct i3c_hci *hci);
void amd_set_od_pp_timing(struct i3c_hci *hci);
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);
#define DEFAULT_AUTOSUSPEND_DELAY_MS 1000
int i3c_hci_rpm_suspend(struct device *dev);
int i3c_hci_rpm_resume(struct device *dev);
int i3c_hci_reset_and_restore(struct i3c_hci *hci);
#endif
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* I3C HCI Quirks
*
* Copyright 2024 Advanced Micro Devices, Inc.
*
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
* Guruvendra Punugupati <Guruvendra.Punugupati@amd.com>
*/
#include <linux/i3c/master.h>
#include "hci.h"
/* Timing registers */
#define HCI_SCL_I3C_OD_TIMING 0x214
#define HCI_SCL_I3C_PP_TIMING 0x218
#define HCI_SDA_HOLD_SWITCH_DLY_TIMING 0x230
/* Timing values to configure 9MHz frequency */
#define AMD_SCL_I3C_OD_TIMING 0x00cf00cf
#define AMD_SCL_I3C_PP_TIMING 0x00160016
#define QUEUE_THLD_CTRL 0xD0
void amd_set_od_pp_timing(struct i3c_hci *hci)
{
u32 data;
reg_write(HCI_SCL_I3C_OD_TIMING, AMD_SCL_I3C_OD_TIMING);
reg_write(HCI_SCL_I3C_PP_TIMING, AMD_SCL_I3C_PP_TIMING);
data = reg_read(HCI_SDA_HOLD_SWITCH_DLY_TIMING);
/* Configure maximum TX hold time */
data |= W0_MASK(18, 16);
reg_write(HCI_SDA_HOLD_SWITCH_DLY_TIMING, data);
}
void amd_set_resp_buf_thld(struct i3c_hci *hci)
{
u32 data;
data = reg_read(QUEUE_THLD_CTRL);
data = data & ~W0_MASK(15, 8);
reg_write(QUEUE_THLD_CTRL, data);
}
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* Common IBI related stuff
*/
#ifndef IBI_H
#define IBI_H
/*
* IBI Status Descriptor bits
*/
#define IBI_STS BIT(31)
#define IBI_ERROR BIT(30)
#define IBI_STATUS_TYPE BIT(29)
#define IBI_HW_CONTEXT GENMASK(28, 26)
#define IBI_TS BIT(25)
#define IBI_LAST_STATUS BIT(24)
#define IBI_CHUNKS GENMASK(23, 16)
#define IBI_ID GENMASK(15, 8)
#define IBI_TARGET_ADDR GENMASK(15, 9)
#define IBI_TARGET_RNW BIT(8)
#define IBI_DATA_LENGTH GENMASK(7, 0)
/* handy helpers */
struct i3c_dev_desc *i3c_hci_addr_to_dev(struct i3c_hci *hci, unsigned int addr);
#endif
@@ -0,0 +1,496 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI glue code for MIPI I3C HCI driver
*
* Copyright (C) 2024 Intel Corporation
*
* Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
*/
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/i3c/master.h>
#include <linux/idr.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/mfd/core.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_qos.h>
#include <linux/pm_runtime.h>
#include "hci.h"
/*
* There can up to 15 instances, but implementations have at most 2 at this
* time.
*/
#define INST_MAX 2
struct mipi_i3c_hci_pci_instance {
struct device *dev;
bool operational;
};
struct mipi_i3c_hci_pci {
struct pci_dev *pci;
void __iomem *base;
const struct mipi_i3c_hci_pci_info *info;
struct mipi_i3c_hci_pci_instance instance[INST_MAX];
void *private;
};
struct mipi_i3c_hci_pci_info {
int (*init)(struct mipi_i3c_hci_pci *hci);
void (*exit)(struct mipi_i3c_hci_pci *hci);
const char *name;
int id[INST_MAX];
u32 instance_offset[INST_MAX];
int instance_count;
bool control_instance_pm;
};
#define INTEL_PRIV_OFFSET 0x2b0
#define INTEL_RESETS 0x04
#define INTEL_RESETS_RESET BIT(0)
#define INTEL_RESETS_RESET_DONE BIT(1)
#define INTEL_RESETS_TIMEOUT_US (10 * USEC_PER_MSEC)
#define INTEL_ACTIVELTR 0x0c
#define INTEL_IDLELTR 0x10
#define INTEL_LTR_REQ BIT(15)
#define INTEL_LTR_SCALE_MASK GENMASK(11, 10)
#define INTEL_LTR_SCALE_1US FIELD_PREP(INTEL_LTR_SCALE_MASK, 2)
#define INTEL_LTR_SCALE_32US FIELD_PREP(INTEL_LTR_SCALE_MASK, 3)
#define INTEL_LTR_VALUE_MASK GENMASK(9, 0)
struct intel_host {
void __iomem *priv;
u32 active_ltr;
u32 idle_ltr;
struct dentry *debugfs_root;
};
static void intel_cache_ltr(struct intel_host *host)
{
host->active_ltr = readl(host->priv + INTEL_ACTIVELTR);
host->idle_ltr = readl(host->priv + INTEL_IDLELTR);
}
static void intel_ltr_set(struct device *dev, s32 val)
{
struct mipi_i3c_hci_pci *hci = dev_get_drvdata(dev);
struct intel_host *host = hci->private;
u32 ltr;
/*
* Program latency tolerance (LTR) accordingly what has been asked
* by the PM QoS layer or disable it in case we were passed
* negative value or PM_QOS_LATENCY_ANY.
*/
ltr = readl(host->priv + INTEL_ACTIVELTR);
if (val == PM_QOS_LATENCY_ANY || val < 0) {
ltr &= ~INTEL_LTR_REQ;
} else {
ltr |= INTEL_LTR_REQ;
ltr &= ~INTEL_LTR_SCALE_MASK;
ltr &= ~INTEL_LTR_VALUE_MASK;
if (val > INTEL_LTR_VALUE_MASK) {
val >>= 5;
if (val > INTEL_LTR_VALUE_MASK)
val = INTEL_LTR_VALUE_MASK;
ltr |= INTEL_LTR_SCALE_32US | val;
} else {
ltr |= INTEL_LTR_SCALE_1US | val;
}
}
if (ltr == host->active_ltr)
return;
writel(ltr, host->priv + INTEL_ACTIVELTR);
writel(ltr, host->priv + INTEL_IDLELTR);
/* Cache the values into intel_host structure */
intel_cache_ltr(host);
}
static void intel_ltr_expose(struct device *dev)
{
dev->power.set_latency_tolerance = intel_ltr_set;
dev_pm_qos_expose_latency_tolerance(dev);
}
static void intel_ltr_hide(struct device *dev)
{
dev_pm_qos_hide_latency_tolerance(dev);
dev->power.set_latency_tolerance = NULL;
}
static void intel_add_debugfs(struct mipi_i3c_hci_pci *hci)
{
struct dentry *dir = debugfs_create_dir(dev_name(&hci->pci->dev), NULL);
struct intel_host *host = hci->private;
intel_cache_ltr(host);
host->debugfs_root = dir;
debugfs_create_x32("active_ltr", 0444, dir, &host->active_ltr);
debugfs_create_x32("idle_ltr", 0444, dir, &host->idle_ltr);
}
static void intel_remove_debugfs(struct mipi_i3c_hci_pci *hci)
{
struct intel_host *host = hci->private;
debugfs_remove_recursive(host->debugfs_root);
}
static void intel_reset(void __iomem *priv)
{
u32 reg;
/* Assert reset, wait for completion and release reset */
writel(0, priv + INTEL_RESETS);
readl_poll_timeout(priv + INTEL_RESETS, reg,
reg & INTEL_RESETS_RESET_DONE, 0,
INTEL_RESETS_TIMEOUT_US);
writel(INTEL_RESETS_RESET, priv + INTEL_RESETS);
}
static int intel_i3c_init(struct mipi_i3c_hci_pci *hci)
{
struct intel_host *host = devm_kzalloc(&hci->pci->dev, sizeof(*host), GFP_KERNEL);
void __iomem *priv = hci->base + INTEL_PRIV_OFFSET;
if (!host)
return -ENOMEM;
dma_set_mask_and_coherent(&hci->pci->dev, DMA_BIT_MASK(64));
hci->pci->d3cold_delay = 0;
hci->pci->d3hot_delay = 0;
hci->private = host;
host->priv = priv;
intel_reset(priv);
intel_ltr_expose(&hci->pci->dev);
intel_add_debugfs(hci);
return 0;
}
static void intel_i3c_exit(struct mipi_i3c_hci_pci *hci)
{
intel_remove_debugfs(hci);
intel_ltr_hide(&hci->pci->dev);
}
static const struct mipi_i3c_hci_pci_info intel_mi_1_info = {
.init = intel_i3c_init,
.exit = intel_i3c_exit,
.name = "intel-lpss-i3c",
.id = {0, 1},
.instance_offset = {0, 0x400},
.instance_count = 2,
.control_instance_pm = true,
};
static const struct mipi_i3c_hci_pci_info intel_mi_2_info = {
.init = intel_i3c_init,
.exit = intel_i3c_exit,
.name = "intel-lpss-i3c",
.id = {2, 3},
.instance_offset = {0, 0x400},
.instance_count = 2,
.control_instance_pm = true,
};
static const struct mipi_i3c_hci_pci_info intel_si_2_info = {
.init = intel_i3c_init,
.exit = intel_i3c_exit,
.name = "intel-lpss-i3c",
.id = {2},
.instance_offset = {0},
.instance_count = 1,
.control_instance_pm = true,
};
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++) {
if (!hci->instance[i].dev)
hci->instance[i].dev = dev;
if (hci->instance[i].dev == dev)
return i;
}
return -1;
}
#define HC_CONTROL 0x04
#define HC_CONTROL_BUS_ENABLE BIT(31)
static bool __mipi_i3c_hci_pci_is_operational(struct device *dev)
{
const struct mipi_i3c_hci_platform_data *pdata = dev->platform_data;
u32 hc_control = readl(pdata->base_regs + HC_CONTROL);
return hc_control & HC_CONTROL_BUS_ENABLE;
}
static bool mipi_i3c_hci_pci_is_operational(struct device *dev, bool update)
{
struct mipi_i3c_hci_pci *hci = dev_get_drvdata(dev->parent);
int pos = mipi_i3c_hci_pci_find_instance(hci, dev);
if (pos < 0) {
dev_err(dev, "%s: I3C instance not found\n", __func__);
return false;
}
if (update)
hci->instance[pos].operational = __mipi_i3c_hci_pci_is_operational(dev);
return hci->instance[pos].operational;
}
struct mipi_i3c_hci_pci_pm_data {
struct device *dev[INST_MAX];
int dev_cnt;
};
static bool mipi_i3c_hci_pci_is_mfd(struct device *dev)
{
return dev_is_platform(dev) && mfd_get_cell(to_platform_device(dev));
}
static int mipi_i3c_hci_pci_suspend_instance(struct device *dev, void *data)
{
struct mipi_i3c_hci_pci_pm_data *pm_data = data;
int ret;
if (!mipi_i3c_hci_pci_is_mfd(dev) ||
!mipi_i3c_hci_pci_is_operational(dev, true))
return 0;
ret = i3c_hci_rpm_suspend(dev);
if (ret)
return ret;
pm_data->dev[pm_data->dev_cnt++] = dev;
return 0;
}
static int mipi_i3c_hci_pci_resume_instance(struct device *dev, void *data)
{
struct mipi_i3c_hci_pci_pm_data *pm_data = data;
int ret;
if (!mipi_i3c_hci_pci_is_mfd(dev) ||
!mipi_i3c_hci_pci_is_operational(dev, false))
return 0;
ret = i3c_hci_rpm_resume(dev);
if (ret)
return ret;
pm_data->dev[pm_data->dev_cnt++] = dev;
return 0;
}
static int mipi_i3c_hci_pci_suspend(struct device *dev)
{
struct mipi_i3c_hci_pci *hci = dev_get_drvdata(dev);
struct mipi_i3c_hci_pci_pm_data pm_data = {};
int ret;
if (!hci->info->control_instance_pm)
return 0;
ret = device_for_each_child_reverse(dev, &pm_data, mipi_i3c_hci_pci_suspend_instance);
if (ret)
for (int i = 0; i < pm_data.dev_cnt; i++)
i3c_hci_rpm_resume(pm_data.dev[i]);
return ret;
}
static int mipi_i3c_hci_pci_resume(struct device *dev)
{
struct mipi_i3c_hci_pci *hci = dev_get_drvdata(dev);
struct mipi_i3c_hci_pci_pm_data pm_data = {};
int ret;
if (!hci->info->control_instance_pm)
return 0;
ret = device_for_each_child(dev, &pm_data, mipi_i3c_hci_pci_resume_instance);
if (ret)
for (int i = 0; i < pm_data.dev_cnt; i++)
i3c_hci_rpm_suspend(pm_data.dev[i]);
return ret;
}
static void mipi_i3c_hci_pci_rpm_allow(struct device *dev)
{
pm_runtime_put(dev);
pm_runtime_allow(dev);
}
static void mipi_i3c_hci_pci_rpm_forbid(struct device *dev)
{
pm_runtime_forbid(dev);
pm_runtime_get_sync(dev);
}
struct mipi_i3c_hci_pci_cell_data {
struct mipi_i3c_hci_platform_data pdata;
struct resource res;
};
static void mipi_i3c_hci_pci_setup_cell(struct mipi_i3c_hci_pci *hci, int idx,
struct mipi_i3c_hci_pci_cell_data *data,
struct mfd_cell *cell)
{
data->pdata.base_regs = hci->base + hci->info->instance_offset[idx];
data->res = DEFINE_RES_IRQ(0);
cell->name = hci->info->name;
cell->id = hci->info->id[idx];
cell->platform_data = &data->pdata;
cell->pdata_size = sizeof(data->pdata);
cell->num_resources = 1;
cell->resources = &data->res;
}
#define mipi_i3c_hci_pci_alloc(h, x) kzalloc_objs(*(x), (h)->info->instance_count)
static int mipi_i3c_hci_pci_add_instances(struct mipi_i3c_hci_pci *hci)
{
struct mipi_i3c_hci_pci_cell_data *data __free(kfree) = mipi_i3c_hci_pci_alloc(hci, data);
struct mfd_cell *cells __free(kfree) = mipi_i3c_hci_pci_alloc(hci, cells);
int irq = pci_irq_vector(hci->pci, 0);
int nr = hci->info->instance_count;
if (!cells || !data)
return -ENOMEM;
for (int i = 0; i < nr; i++)
mipi_i3c_hci_pci_setup_cell(hci, i, data + i, cells + i);
return mfd_add_devices(&hci->pci->dev, 0, cells, nr, NULL, irq, NULL);
}
static int mipi_i3c_hci_pci_probe(struct pci_dev *pci,
const struct pci_device_id *id)
{
struct mipi_i3c_hci_pci *hci;
int ret;
hci = devm_kzalloc(&pci->dev, sizeof(*hci), GFP_KERNEL);
if (!hci)
return -ENOMEM;
hci->pci = pci;
ret = pcim_enable_device(pci);
if (ret)
return ret;
pci_set_master(pci);
hci->base = pcim_iomap_region(pci, 0, pci_name(pci));
if (IS_ERR(hci->base))
return PTR_ERR(hci->base);
ret = pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_ALL_TYPES);
if (ret < 0)
return ret;
hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data;
ret = hci->info->init ? hci->info->init(hci) : 0;
if (ret)
return ret;
ret = mipi_i3c_hci_pci_add_instances(hci);
if (ret)
goto err_exit;
pci_set_drvdata(pci, hci);
mipi_i3c_hci_pci_rpm_allow(&pci->dev);
return 0;
err_exit:
if (hci->info->exit)
hci->info->exit(hci);
return ret;
}
static void mipi_i3c_hci_pci_remove(struct pci_dev *pci)
{
struct mipi_i3c_hci_pci *hci = pci_get_drvdata(pci);
if (hci->info->exit)
hci->info->exit(hci);
mipi_i3c_hci_pci_rpm_forbid(&pci->dev);
mfd_remove_devices(&pci->dev);
}
/* PM ops must exist for PCI to put a device to a low power state */
static const struct dev_pm_ops mipi_i3c_hci_pci_pm_ops = {
RUNTIME_PM_OPS(mipi_i3c_hci_pci_suspend, mipi_i3c_hci_pci_resume, NULL)
SYSTEM_SLEEP_PM_OPS(mipi_i3c_hci_pci_suspend, mipi_i3c_hci_pci_resume)
};
static const struct pci_device_id mipi_i3c_hci_pci_devices[] = {
/* Wildcat Lake-U */
{ PCI_VDEVICE(INTEL, 0x4d7c), .driver_data = (kernel_ulong_t)&intel_mi_1_info },
{ PCI_VDEVICE(INTEL, 0x4d6f), .driver_data = (kernel_ulong_t)&intel_si_2_info },
/* Panther Lake-H */
{ PCI_VDEVICE(INTEL, 0xe37c), .driver_data = (kernel_ulong_t)&intel_mi_1_info },
{ PCI_VDEVICE(INTEL, 0xe36f), .driver_data = (kernel_ulong_t)&intel_si_2_info },
/* Panther Lake-P */
{ PCI_VDEVICE(INTEL, 0xe47c), .driver_data = (kernel_ulong_t)&intel_mi_1_info },
{ PCI_VDEVICE(INTEL, 0xe46f), .driver_data = (kernel_ulong_t)&intel_si_2_info },
/* Nova Lake-S */
{ PCI_VDEVICE(INTEL, 0x6e2c), .driver_data = (kernel_ulong_t)&intel_mi_1_info },
{ PCI_VDEVICE(INTEL, 0x6e2d), .driver_data = (kernel_ulong_t)&intel_mi_2_info },
/* 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 },
{ }
};
MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices);
static struct pci_driver mipi_i3c_hci_pci_driver = {
.name = "mipi_i3c_hci_pci",
.id_table = mipi_i3c_hci_pci_devices,
.probe = mipi_i3c_hci_pci_probe,
.remove = mipi_i3c_hci_pci_remove,
.driver = {
.pm = pm_ptr(&mipi_i3c_hci_pci_pm_ops)
},
};
module_pci_driver(mipi_i3c_hci_pci_driver);
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@intel.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MIPI I3C HCI driver on PCI bus");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (c) 2020, MIPI Alliance, Inc.
*
* Author: Nicolas Pitre <npitre@baylibre.com>
*
* Transfer Mode/Rate Table definitions as found in extended capability
* sections 0x04 and 0x08.
* This applies starting from I3C HCI v2.0.
*/
#ifndef XFER_MODE_RATE_H
#define XFER_MODE_RATE_H
/*
* Master Transfer Mode Table Fixed Indexes.
*
* Indexes 0x0 and 0x8 are mandatory. Availability for the rest must be
* obtained from the mode table in the extended capability area.
* Presence and definitions for indexes beyond these ones may vary.
*/
#define XFERMODE_IDX_I3C_SDR 0x00 /* I3C SDR Mode */
#define XFERMODE_IDX_I3C_HDR_DDR 0x01 /* I3C HDR-DDR Mode */
#define XFERMODE_IDX_I3C_HDR_T 0x02 /* I3C HDR-Ternary Mode */
#define XFERMODE_IDX_I3C_HDR_BT 0x03 /* I3C HDR-BT Mode */
#define XFERMODE_IDX_I2C 0x08 /* Legacy I2C Mode */
/*
* Transfer Mode Table Entry Bits Definitions
*/
#define XFERMODE_VALID_XFER_ADD_FUNC GENMASK(21, 16)
#define XFERMODE_ML_DATA_XFER_CODING GENMASK(15, 11)
#define XFERMODE_ML_ADDL_LANES GENMASK(10, 8)
#define XFERMODE_SUPPORTED BIT(7)
#define XFERMODE_MODE GENMASK(3, 0)
/*
* Master Data Transfer Rate Selector Values.
*
* These are the values to be used in the command descriptor XFER_RATE field
* and found in the RATE_ID field below.
* The I3C_SDR0, I3C_SDR1, I3C_SDR2, I3C_SDR3, I3C_SDR4 and I2C_FM rates
* are required, everything else is optional and discoverable in the
* Data Transfer Rate Table. Indicated are typical rates. The actual
* rates may vary slightly and are also specified in the Data Transfer
* Rate Table.
*/
#define XFERRATE_I3C_SDR0 0x00 /* 12.5 MHz */
#define XFERRATE_I3C_SDR1 0x01 /* 8 MHz */
#define XFERRATE_I3C_SDR2 0x02 /* 6 MHz */
#define XFERRATE_I3C_SDR3 0x03 /* 4 MHz */
#define XFERRATE_I3C_SDR4 0x04 /* 2 MHz */
#define XFERRATE_I3C_SDR_FM_FMP 0x05 /* 400 KHz / 1 MHz */
#define XFERRATE_I3C_SDR_USER6 0x06 /* User Defined */
#define XFERRATE_I3C_SDR_USER7 0x07 /* User Defined */
#define XFERRATE_I2C_FM 0x00 /* 400 KHz */
#define XFERRATE_I2C_FMP 0x01 /* 1 MHz */
#define XFERRATE_I2C_USER2 0x02 /* User Defined */
#define XFERRATE_I2C_USER3 0x03 /* User Defined */
#define XFERRATE_I2C_USER4 0x04 /* User Defined */
#define XFERRATE_I2C_USER5 0x05 /* User Defined */
#define XFERRATE_I2C_USER6 0x06 /* User Defined */
#define XFERRATE_I2C_USER7 0x07 /* User Defined */
/*
* Master Data Transfer Rate Table Mode ID values.
*/
#define XFERRATE_MODE_I3C 0x00
#define XFERRATE_MODE_I2C 0x08
/*
* Master Data Transfer Rate Table Entry Bits Definitions
*/
#define XFERRATE_MODE_ID GENMASK(31, 28)
#define XFERRATE_RATE_ID GENMASK(22, 20)
#define XFERRATE_ACTUAL_RATE_KHZ GENMASK(19, 0)
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff