I3C for 6.19

Subsystem:
  - Add HDR transfer support
 
 Drivers:
  - dw: fix bus hang on Agilex5
  - mipi-i3c-hci: Intel Nova Lake-S support, IOMMU support
  - svc: HDR support
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEBqsFVZXh8s/0O5JiY6TcMGxwOjIFAmk2MWUACgkQY6TcMGxw
 OjKkBQ/8C7lUVwuCThYHjD1tXamYiZ3iTE2TP3rcn6w6VdFrvZq0I/D3Z67ze0PR
 kRwC9VRgV7qrOgu7niubRxUNkAsLkVvJBGGf3QKGaozqhGaqnhxoOIGZGVehG0GR
 DuuR6eCkiIFyG1Z+8rBROkx2K84ne81SPsZ1/E9bB9GJXW1aALJ8PgAblu5VPecO
 4YtqVRS8JOxTx5c0fnlpYo5xn3M0jMIhtAI9Z7AAGj0ZvoBaPplHjSM5L/YJCKss
 KlGpuNMGCS25PsnujDmELn4QStIbMmCOYA6hiRvVk3iDf4zcG4f0Rem+Tr9jstUe
 t8h8/s7maUs01i2rVBZtcanAY1iYbECiBYmRTHowDcJDj+6/BAz6cKNz7eDv7THl
 tcPz4RotIAJOPTof9u9va0wt+LcFh6Y6SXVFEP3RYcFrgyjECFjJ+PL0TKIiJOyo
 pRFgmyAU/qakmfcLEuWe95WJyl27d/pyg6WW4iuKDiVU8/um5M8RxlBCcBiLzmrM
 j3XOAobE4KSpI/2deBxNdIpDNQyGTxNAyDobPQPU2nrQA0k/w8UmksQF/ZO+/RzM
 5ZNfqSMhHO4j6GBIbo0iD+E2hO8l/7awMpiQZyhTrTrmFcBlVdbGttxlk+YYFKa7
 7O3VILhoZZZppKIlLike6eOWFm+CiQ11zIGfHj7mnpAy5zggr3g=
 =qQUB
 -----END PGP SIGNATURE-----

Merge tag 'i3c/for-6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux

Pull i3c updates from Alexandre Belloni:
 "HDR support has finally been added. mipi-i3c-hci has been reworked and
  Intel Nova Lake-S support has been added.

  Subsystem:
   - Add HDR transfer support

  Drivers:
   - dw: fix bus hang on Agilex5
   - mipi-i3c-hci: Intel Nova Lake-S support, IOMMU support
   - svc: HDR support"

* tag 'i3c/for-6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (28 commits)
  regmap: i3c: switch to use i3c_xfer from i3c_priv_xfer
  net: mctp i3c: switch to use i3c_xfer from i3c_priv_xfer
  hwmon: (lm75): switch to use i3c_xfer from i3c_priv_xfer
  i3c: document i3c_xfers
  i3c: fix I3C_SDR bit number
  i3c: master: svc: Add basic HDR mode support
  i3c: master: svc: Replace bool rnw with union for HDR support
  i3c: Switch to use new i3c_xfer from i3c_priv_xfer
  i3c: Add HDR API support
  i3c: master: add WQ_PERCPU to alloc_workqueue users
  i3c: master: Remove i3c_device_free_ibi from i3c_device_remove
  i3c: mipi-i3c-hci-pci: Set d3cold_delay to 0 for Intel controllers
  i3c: mipi-i3c-hci-pci: Add LTR support for Intel controllers
  i3c: mipi-i3c-hci-pci: Add exit callback
  i3c: mipi-i3c-hci-pci: Change callback parameter
  i3c: mipi-i3c-hci-pci: Allocate a structure for mipi_i3c_hci_pci device information
  i3c: mipi-i3c-hci-pci: Factor out intel_reset()
  i3c: mipi-i3c-hci-pci: Factor out private registers ioremapping
  i3c: mipi-i3c-hci-pci: Constify driver data
  i3c: mipi-i3c-hci-pci: Use readl_poll_timeout()
  ...
pull/1354/merge
Linus Torvalds 2025-12-08 11:25:14 +09:00
commit c2f2b01b74
12 changed files with 427 additions and 116 deletions

View File

@ -14,7 +14,11 @@ allOf:
properties:
compatible:
const: snps,dw-i3c-master-1.00a
oneOf:
- const: snps,dw-i3c-master-1.00a
- items:
- const: altr,agilex5-dw-i3c-master
- const: snps,dw-i3c-master-1.00a
reg:
maxItems: 1

View File

@ -11,7 +11,7 @@ static int regmap_i3c_write(void *context, const void *data, size_t count)
{
struct device *dev = context;
struct i3c_device *i3c = dev_to_i3cdev(dev);
struct i3c_priv_xfer xfers[] = {
struct i3c_xfer xfers[] = {
{
.rnw = false,
.len = count,
@ -19,7 +19,7 @@ static int regmap_i3c_write(void *context, const void *data, size_t count)
},
};
return i3c_device_do_priv_xfers(i3c, xfers, ARRAY_SIZE(xfers));
return i3c_device_do_xfers(i3c, xfers, ARRAY_SIZE(xfers), I3C_SDR);
}
static int regmap_i3c_read(void *context,
@ -28,7 +28,7 @@ static int regmap_i3c_read(void *context,
{
struct device *dev = context;
struct i3c_device *i3c = dev_to_i3cdev(dev);
struct i3c_priv_xfer xfers[2];
struct i3c_xfer xfers[2];
xfers[0].rnw = false;
xfers[0].len = reg_size;
@ -38,7 +38,7 @@ static int regmap_i3c_read(void *context,
xfers[1].len = val_size;
xfers[1].data.in = val;
return i3c_device_do_priv_xfers(i3c, xfers, ARRAY_SIZE(xfers));
return i3c_device_do_xfers(i3c, xfers, ARRAY_SIZE(xfers), I3C_SDR);
}
static const struct regmap_bus regmap_i3c = {

View File

@ -621,7 +621,7 @@ static int lm75_i3c_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct i3c_device *i3cdev = context;
struct lm75_data *data = i3cdev_get_drvdata(i3cdev);
struct i3c_priv_xfer xfers[] = {
struct i3c_xfer xfers[] = {
{
.rnw = false,
.len = 1,
@ -640,7 +640,7 @@ static int lm75_i3c_reg_read(void *context, unsigned int reg, unsigned int *val)
if (reg == LM75_REG_CONF && !data->params->config_reg_16bits)
xfers[1].len--;
ret = i3c_device_do_priv_xfers(i3cdev, xfers, 2);
ret = i3c_device_do_xfers(i3cdev, xfers, 2, I3C_SDR);
if (ret < 0)
return ret;
@ -658,7 +658,7 @@ static int lm75_i3c_reg_write(void *context, unsigned int reg, unsigned int val)
{
struct i3c_device *i3cdev = context;
struct lm75_data *data = i3cdev_get_drvdata(i3cdev);
struct i3c_priv_xfer xfers[] = {
struct i3c_xfer xfers[] = {
{
.rnw = false,
.len = 3,
@ -680,7 +680,7 @@ static int lm75_i3c_reg_write(void *context, unsigned int reg, unsigned int val)
data->val_buf[2] = val & 0xff;
}
return i3c_device_do_priv_xfers(i3cdev, xfers, 1);
return i3c_device_do_xfers(i3cdev, xfers, 1, I3C_SDR);
}
static const struct regmap_bus lm75_i3c_regmap_bus = {

View File

@ -15,12 +15,12 @@
#include "internals.h"
/**
* i3c_device_do_priv_xfers() - do I3C SDR private transfers directed to a
* specific device
* i3c_device_do_xfers() - do I3C transfers directed to a specific device
*
* @dev: device with which the transfers should be done
* @xfers: array of transfers
* @nxfers: number of transfers
* @mode: transfer mode
*
* Initiate one or several private SDR transfers with @dev.
*
@ -33,9 +33,8 @@
* 'xfers' some time later. See I3C spec ver 1.1.1 09-Jun-2021. Section:
* 5.1.2.2.3.
*/
int i3c_device_do_priv_xfers(struct i3c_device *dev,
struct i3c_priv_xfer *xfers,
int nxfers)
int i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers,
int nxfers, enum i3c_xfer_mode mode)
{
int ret, i;
@ -48,12 +47,12 @@ int i3c_device_do_priv_xfers(struct i3c_device *dev,
}
i3c_bus_normaluse_lock(dev->bus);
ret = i3c_dev_do_priv_xfers_locked(dev->desc, xfers, nxfers);
ret = i3c_dev_do_xfers_locked(dev->desc, xfers, nxfers, mode);
i3c_bus_normaluse_unlock(dev->bus);
return ret;
}
EXPORT_SYMBOL_GPL(i3c_device_do_priv_xfers);
EXPORT_SYMBOL_GPL(i3c_device_do_xfers);
/**
* i3c_device_do_setdasa() - do I3C dynamic address assignement with
@ -260,6 +259,20 @@ i3c_device_match_id(struct i3c_device *i3cdev,
}
EXPORT_SYMBOL_GPL(i3c_device_match_id);
/**
* i3c_device_get_supported_xfer_mode - Returns the supported transfer mode by
* connected master controller.
* @dev: I3C device
*
* Return: a bit mask, which supported transfer mode, bit position is defined at
* enum i3c_hdr_mode
*/
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);
}
EXPORT_SYMBOL_GPL(i3c_device_get_supported_xfer_mode);
/**
* i3c_driver_register_with_owner() - register an I3C device driver
*

View File

@ -15,9 +15,9 @@ void i3c_bus_normaluse_lock(struct i3c_bus *bus);
void i3c_bus_normaluse_unlock(struct i3c_bus *bus);
int i3c_dev_setdasa_locked(struct i3c_dev_desc *dev);
int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
struct i3c_priv_xfer *xfers,
int nxfers);
int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev,
struct i3c_xfer *xfers,
int nxfers, enum i3c_xfer_mode mode);
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev);
int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev);
int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,

View File

@ -334,8 +334,6 @@ static void i3c_device_remove(struct device *dev)
if (driver->remove)
driver->remove(i3cdev);
i3c_device_free_ibi(i3cdev);
}
const struct bus_type i3c_bus_type = {
@ -2821,10 +2819,14 @@ EXPORT_SYMBOL_GPL(i3c_generic_ibi_recycle_slot);
static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops)
{
if (!ops || !ops->bus_init || !ops->priv_xfers ||
if (!ops || !ops->bus_init ||
!ops->send_ccc_cmd || !ops->do_daa || !ops->i2c_xfers)
return -EINVAL;
/* Must provide one of priv_xfers (SDR only) or i3c_xfers (all modes) */
if (!ops->priv_xfers && !ops->i3c_xfers)
return -EINVAL;
if (ops->request_ibi &&
(!ops->enable_ibi || !ops->disable_ibi || !ops->free_ibi ||
!ops->recycle_ibi_slot))
@ -2883,10 +2885,6 @@ int i3c_master_register(struct i3c_master_controller *master,
INIT_LIST_HEAD(&master->boardinfo.i2c);
INIT_LIST_HEAD(&master->boardinfo.i3c);
ret = i3c_bus_init(i3cbus, master->dev.of_node);
if (ret)
return ret;
device_initialize(&master->dev);
dev_set_name(&master->dev, "i3c-%d", i3cbus->id);
@ -2894,6 +2892,10 @@ int i3c_master_register(struct i3c_master_controller *master,
master->dev.coherent_dma_mask = parent->coherent_dma_mask;
master->dev.dma_parms = parent->dma_parms;
ret = i3c_bus_init(i3cbus, master->dev.of_node);
if (ret)
goto err_put_dev;
ret = of_populate_i3c_bus(master);
if (ret)
goto err_put_dev;
@ -2925,7 +2927,7 @@ int i3c_master_register(struct i3c_master_controller *master,
if (ret)
goto err_put_dev;
master->wq = alloc_workqueue("%s", 0, 0, dev_name(parent));
master->wq = alloc_workqueue("%s", WQ_PERCPU, 0, dev_name(parent));
if (!master->wq) {
ret = -ENOMEM;
goto err_put_dev;
@ -3014,9 +3016,8 @@ int i3c_dev_setdasa_locked(struct i3c_dev_desc *dev)
dev->boardinfo->init_dyn_addr);
}
int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
struct i3c_priv_xfer *xfers,
int nxfers)
int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
int nxfers, enum i3c_xfer_mode mode)
{
struct i3c_master_controller *master;
@ -3027,9 +3028,15 @@ int i3c_dev_do_priv_xfers_locked(struct i3c_dev_desc *dev,
if (!master || !xfers)
return -EINVAL;
if (!master->ops->priv_xfers)
if (mode != I3C_SDR && !(master->this->info.hdr_cap & BIT(mode)))
return -EOPNOTSUPP;
if (master->ops->i3c_xfers)
return master->ops->i3c_xfers(dev, xfers, nxfers, mode);
if (mode != I3C_SDR)
return -EINVAL;
return master->ops->priv_xfers(dev, xfers, nxfers);
}

View File

@ -228,6 +228,7 @@
/* List of quirks */
#define AMD_I3C_OD_PP_TIMING BIT(1)
#define DW_I3C_DISABLE_RUNTIME_PM_QUIRK BIT(2)
struct dw_i3c_cmd {
u32 cmd_lo;
@ -252,6 +253,10 @@ struct dw_i3c_i2c_dev_data {
struct i3c_generic_ibi_pool *ibi_pool;
};
struct dw_i3c_drvdata {
u32 flags;
};
static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
const struct i3c_ccc_cmd *cmd)
{
@ -1535,6 +1540,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
struct platform_device *pdev)
{
int ret, irq;
const struct dw_i3c_drvdata *drvdata;
unsigned long quirks = 0;
if (!master->platform_ops)
master->platform_ops = &dw_i3c_platform_ops_default;
@ -1590,7 +1597,18 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
master->maxdevs = ret >> 16;
master->free_pos = GENMASK(master->maxdevs - 1, 0);
master->quirks = (unsigned long)device_get_match_data(&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;
/* Keep controller enabled by preventing runtime suspend */
if (master->quirks & DW_I3C_DISABLE_RUNTIME_PM_QUIRK)
pm_runtime_get_noresume(&pdev->dev);
INIT_WORK(&master->hj_work, dw_i3c_hj_work);
ret = i3c_master_register(&master->base, &pdev->dev,
@ -1617,6 +1635,10 @@ void dw_i3c_common_remove(struct dw_i3c_master *master)
cancel_work_sync(&master->hj_work);
i3c_master_unregister(&master->base);
/* Balance pm_runtime_get_noresume() from probe() */
if (master->quirks & DW_I3C_DISABLE_RUNTIME_PM_QUIRK)
pm_runtime_put_noidle(master->dev);
pm_runtime_disable(master->dev);
pm_runtime_set_suspended(master->dev);
pm_runtime_dont_use_autosuspend(master->dev);
@ -1759,8 +1781,15 @@ static void dw_i3c_shutdown(struct platform_device *pdev)
pm_runtime_put_autosuspend(master->dev);
}
static const struct dw_i3c_drvdata altr_agilex5_drvdata = {
.flags = DW_I3C_DISABLE_RUNTIME_PM_QUIRK,
};
static const struct of_device_id dw_i3c_master_of_match[] = {
{ .compatible = "snps,dw-i3c-master-1.00a", },
{ .compatible = "altr,agilex5-dw-i3c-master",
.data = &altr_agilex5_drvdata,
},
{},
};
MODULE_DEVICE_TABLE(of, dw_i3c_master_of_match);

View File

@ -7,61 +7,196 @@
* Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
*/
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/idr.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/pm_qos.h>
struct mipi_i3c_hci_pci_info {
int (*init)(struct pci_dev *pci);
struct mipi_i3c_hci_pci {
struct pci_dev *pci;
struct platform_device *pdev;
const struct mipi_i3c_hci_pci_info *info;
void *private;
};
#define INTEL_PRIV_OFFSET 0x2b0
#define INTEL_PRIV_SIZE 0x28
#define INTEL_PRIV_RESETS 0x04
#define INTEL_PRIV_RESETS_RESET BIT(0)
#define INTEL_PRIV_RESETS_RESET_DONE BIT(1)
struct mipi_i3c_hci_pci_info {
int (*init)(struct mipi_i3c_hci_pci *hci);
void (*exit)(struct mipi_i3c_hci_pci *hci);
};
static DEFINE_IDA(mipi_i3c_hci_pci_ida);
static int mipi_i3c_hci_pci_intel_init(struct pci_dev *pci)
{
unsigned long timeout;
void __iomem *priv;
#define INTEL_PRIV_OFFSET 0x2b0
#define INTEL_PRIV_SIZE 0x28
#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)
priv = devm_ioremap(&pci->dev,
pci_resource_start(pci, 0) + INTEL_PRIV_OFFSET,
INTEL_PRIV_SIZE);
if (!priv)
return -ENOMEM;
#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_PRIV_RESETS);
timeout = jiffies + msecs_to_jiffies(10);
while (!(readl(priv + INTEL_PRIV_RESETS) &
INTEL_PRIV_RESETS_RESET_DONE)) {
if (time_after(jiffies, timeout))
break;
cpu_relax();
}
writel(INTEL_PRIV_RESETS_RESET, priv + INTEL_PRIV_RESETS);
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 void __iomem *intel_priv(struct pci_dev *pci)
{
resource_size_t base = pci_resource_start(pci, 0);
return devm_ioremap(&pci->dev, base + INTEL_PRIV_OFFSET, INTEL_PRIV_SIZE);
}
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 = intel_priv(hci->pci);
if (!host || !priv)
return -ENOMEM;
dma_set_mask_and_coherent(&hci->pci->dev, DMA_BIT_MASK(64));
hci->pci->d3cold_delay = 0;
hci->private = host;
host->priv = priv;
intel_reset(priv);
intel_ltr_expose(&hci->pci->dev);
intel_add_debugfs(hci);
return 0;
}
static struct mipi_i3c_hci_pci_info intel_info = {
.init = mipi_i3c_hci_pci_intel_init,
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_info = {
.init = intel_i3c_init,
.exit = intel_i3c_exit,
};
static int mipi_i3c_hci_pci_probe(struct pci_dev *pci,
const struct pci_device_id *id)
{
struct mipi_i3c_hci_pci_info *info;
struct platform_device *pdev;
struct mipi_i3c_hci_pci *hci;
struct resource res[2];
int dev_id, 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;
@ -82,43 +217,50 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci,
if (dev_id < 0)
return dev_id;
pdev = platform_device_alloc("mipi-i3c-hci", dev_id);
if (!pdev)
hci->pdev = platform_device_alloc("mipi-i3c-hci", dev_id);
if (!hci->pdev)
return -ENOMEM;
pdev->dev.parent = &pci->dev;
device_set_node(&pdev->dev, dev_fwnode(&pci->dev));
hci->pdev->dev.parent = &pci->dev;
device_set_node(&hci->pdev->dev, dev_fwnode(&pci->dev));
ret = platform_device_add_resources(pdev, res, ARRAY_SIZE(res));
ret = platform_device_add_resources(hci->pdev, res, ARRAY_SIZE(res));
if (ret)
goto err;
info = (struct mipi_i3c_hci_pci_info *)id->driver_data;
if (info && info->init) {
ret = info->init(pci);
hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data;
if (hci->info && hci->info->init) {
ret = hci->info->init(hci);
if (ret)
goto err;
}
ret = platform_device_add(pdev);
ret = platform_device_add(hci->pdev);
if (ret)
goto err;
goto err_exit;
pci_set_drvdata(pci, pdev);
pci_set_drvdata(pci, hci);
return 0;
err_exit:
if (hci->info && hci->info->exit)
hci->info->exit(hci);
err:
platform_device_put(pdev);
platform_device_put(hci->pdev);
ida_free(&mipi_i3c_hci_pci_ida, dev_id);
return ret;
}
static void mipi_i3c_hci_pci_remove(struct pci_dev *pci)
{
struct platform_device *pdev = pci_get_drvdata(pci);
struct mipi_i3c_hci_pci *hci = pci_get_drvdata(pci);
struct platform_device *pdev = hci->pdev;
int dev_id = pdev->id;
if (hci->info && hci->info->exit)
hci->info->exit(hci);
platform_device_unregister(pdev);
ida_free(&mipi_i3c_hci_pci_ida, dev_id);
}
@ -133,6 +275,9 @@ static const struct pci_device_id mipi_i3c_hci_pci_devices[] = {
/* Panther Lake-P */
{ PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_info},
{ PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_info},
/* Nova Lake-S */
{ PCI_VDEVICE(INTEL, 0x6e2c), (kernel_ulong_t)&intel_info},
{ PCI_VDEVICE(INTEL, 0x6e2d), (kernel_ulong_t)&intel_info},
{ },
};
MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices);

View File

@ -40,11 +40,13 @@
#define SVC_I3C_MCTRL_REQUEST_NONE 0
#define SVC_I3C_MCTRL_REQUEST_START_ADDR 1
#define SVC_I3C_MCTRL_REQUEST_STOP 2
#define SVC_I3C_MCTRL_REQUEST_FORCE_EXIT 6
#define SVC_I3C_MCTRL_REQUEST_IBI_ACKNACK 3
#define SVC_I3C_MCTRL_REQUEST_PROC_DAA 4
#define SVC_I3C_MCTRL_REQUEST_AUTO_IBI 7
#define SVC_I3C_MCTRL_TYPE_I3C 0
#define SVC_I3C_MCTRL_TYPE_I2C BIT(4)
#define SVC_I3C_MCTRL_TYPE_DDR BIT(5)
#define SVC_I3C_MCTRL_IBIRESP_AUTO 0
#define SVC_I3C_MCTRL_IBIRESP_ACK_WITHOUT_BYTE 0
#define SVC_I3C_MCTRL_IBIRESP_ACK_WITH_BYTE BIT(7)
@ -95,6 +97,7 @@
#define SVC_I3C_MINTMASKED 0x098
#define SVC_I3C_MERRWARN 0x09C
#define SVC_I3C_MERRWARN_NACK BIT(2)
#define SVC_I3C_MERRWARN_CRC BIT(10)
#define SVC_I3C_MERRWARN_TIMEOUT BIT(20)
#define SVC_I3C_MDMACTRL 0x0A0
#define SVC_I3C_MDATACTRL 0x0AC
@ -165,12 +168,16 @@
struct svc_i3c_cmd {
u8 addr;
bool rnw;
union {
bool rnw;
u8 cmd;
u32 rnw_cmd;
};
u8 *in;
const void *out;
unsigned int len;
unsigned int actual_len;
struct i3c_priv_xfer *xfer;
struct i3c_xfer *xfer;
bool continued;
};
@ -383,6 +390,36 @@ svc_i3c_master_dev_from_addr(struct svc_i3c_master *master,
return master->descs[i];
}
static bool svc_cmd_is_read(u32 rnw_cmd, u32 type)
{
return (type == SVC_I3C_MCTRL_TYPE_DDR) ? (rnw_cmd & 0x80) : rnw_cmd;
}
static void svc_i3c_master_emit_force_exit(struct svc_i3c_master *master)
{
u32 reg;
writel(SVC_I3C_MCTRL_REQUEST_FORCE_EXIT, master->regs + SVC_I3C_MCTRL);
/*
* Not need check error here because it is never happen at hardware.
* IP just wait for few fclk cycle to complete DDR exit pattern. Even
* though fclk stop, timeout happen here, the whole data actually
* already finish transfer. The next command will be timeout because
* wrong hardware state.
*/
readl_poll_timeout_atomic(master->regs + SVC_I3C_MSTATUS, reg,
SVC_I3C_MSTATUS_MCTRLDONE(reg), 0, 1000);
/*
* This delay is necessary after the emission of a stop, otherwise eg.
* repeating IBIs do not get detected. There is a note in the manual
* about it, stating that the stop condition might not be settled
* correctly if a start condition follows too rapidly.
*/
udelay(1);
}
static void svc_i3c_master_emit_stop(struct svc_i3c_master *master)
{
writel(SVC_I3C_MCTRL_REQUEST_STOP, master->regs + SVC_I3C_MCTRL);
@ -406,21 +443,27 @@ static int svc_i3c_master_handle_ibi(struct svc_i3c_master *master,
int ret, val;
u8 *buf;
slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
if (!slot)
return -ENOSPC;
slot->len = 0;
buf = slot->data;
/*
* Wait for transfer to complete before returning. Otherwise, the EmitStop
* request might be sent when the transfer is not complete.
*/
ret = readl_relaxed_poll_timeout(master->regs + SVC_I3C_MSTATUS, val,
SVC_I3C_MSTATUS_COMPLETE(val), 0, 1000);
if (ret) {
dev_err(master->dev, "Timeout when polling for COMPLETE\n");
i3c_generic_ibi_recycle_slot(data->ibi_pool, slot);
return ret;
}
slot = i3c_generic_ibi_get_free_slot(data->ibi_pool);
if (!slot) {
dev_dbg(master->dev, "No free ibi slot, drop the data\n");
writel(SVC_I3C_MDATACTRL_FLUSHRB, master->regs + SVC_I3C_MDATACTRL);
return -ENOSPC;
}
slot->len = 0;
buf = slot->data;
while (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)) &&
slot->len < SVC_I3C_FIFO_SIZE) {
mdatactrl = readl(master->regs + SVC_I3C_MDATACTRL);
@ -512,7 +555,7 @@ static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master)
* cycle, leading to missed client IBI handlers.
*
* A typical scenario is when IBIWON occurs and bus arbitration is lost
* at svc_i3c_master_priv_xfers().
* at svc_i3c_master_i3c_xfers().
*
* Clear SVC_I3C_MINT_IBIWON before sending SVC_I3C_MCTRL_REQUEST_AUTO_IBI.
*/
@ -792,6 +835,8 @@ static int svc_i3c_master_bus_init(struct i3c_master_controller *m)
info.dyn_addr = ret;
info.hdr_cap = I3C_CCC_HDR_MODE(I3C_HDR_DDR);
writel(SVC_MDYNADDR_VALID | SVC_MDYNADDR_ADDR(info.dyn_addr),
master->regs + SVC_I3C_MDYNADDR);
@ -1293,10 +1338,11 @@ static int svc_i3c_master_write(struct svc_i3c_master *master,
}
static int svc_i3c_master_xfer(struct svc_i3c_master *master,
bool rnw, unsigned int xfer_type, u8 addr,
u32 rnw_cmd, unsigned int xfer_type, u8 addr,
u8 *in, const u8 *out, unsigned int xfer_len,
unsigned int *actual_len, bool continued, bool repeat_start)
{
bool rnw = svc_cmd_is_read(rnw_cmd, xfer_type);
int retry = repeat_start ? 1 : 2;
u32 reg;
int ret;
@ -1304,6 +1350,16 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
/* clean SVC_I3C_MINT_IBIWON w1c bits */
writel(SVC_I3C_MINT_IBIWON, master->regs + SVC_I3C_MSTATUS);
if (xfer_type == SVC_I3C_MCTRL_TYPE_DDR) {
/* DDR command need prefill into FIFO */
writel(rnw_cmd, master->regs + SVC_I3C_MWDATAB);
if (!rnw) {
/* write data also need prefill into FIFO */
ret = svc_i3c_master_write(master, out, xfer_len);
if (ret)
goto emit_stop;
}
}
while (retry--) {
writel(SVC_I3C_MCTRL_REQUEST_START_ADDR |
@ -1397,7 +1453,7 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
if (rnw)
ret = svc_i3c_master_read(master, in, xfer_len);
else
else if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR)
ret = svc_i3c_master_write(master, out, xfer_len);
if (ret < 0)
goto emit_stop;
@ -1410,10 +1466,19 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
if (ret)
goto emit_stop;
if (xfer_type == SVC_I3C_MCTRL_TYPE_DDR &&
(readl(master->regs + SVC_I3C_MERRWARN) & SVC_I3C_MERRWARN_CRC)) {
ret = -ENXIO;
goto emit_stop;
}
writel(SVC_I3C_MINT_COMPLETE, master->regs + SVC_I3C_MSTATUS);
if (!continued) {
svc_i3c_master_emit_stop(master);
if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR)
svc_i3c_master_emit_stop(master);
else
svc_i3c_master_emit_force_exit(master);
/* Wait idle if stop is sent. */
readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
@ -1423,7 +1488,11 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
return 0;
emit_stop:
svc_i3c_master_emit_stop(master);
if (xfer_type != SVC_I3C_MCTRL_TYPE_DDR)
svc_i3c_master_emit_stop(master);
else
svc_i3c_master_emit_force_exit(master);
svc_i3c_master_clear_merrwarn(master);
svc_i3c_master_flush_fifo(master);
@ -1470,6 +1539,11 @@ static void svc_i3c_master_dequeue_xfer(struct svc_i3c_master *master,
spin_unlock_irqrestore(&master->xferqueue.lock, flags);
}
static int i3c_mode_to_svc_type(enum i3c_xfer_mode mode)
{
return (mode == I3C_SDR) ? SVC_I3C_MCTRL_TYPE_I3C : SVC_I3C_MCTRL_TYPE_DDR;
}
static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master)
{
struct svc_i3c_xfer *xfer = master->xferqueue.cur;
@ -1484,7 +1558,7 @@ static void svc_i3c_master_start_xfer_locked(struct svc_i3c_master *master)
for (i = 0; i < xfer->ncmds; i++) {
struct svc_i3c_cmd *cmd = &xfer->cmds[i];
ret = svc_i3c_master_xfer(master, cmd->rnw, xfer->type,
ret = svc_i3c_master_xfer(master, cmd->rnw_cmd, xfer->type,
cmd->addr, cmd->in, cmd->out,
cmd->len, &cmd->actual_len,
cmd->continued, i > 0);
@ -1659,9 +1733,8 @@ static int svc_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
return ret;
}
static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
struct i3c_priv_xfer *xfers,
int nxfers)
static int svc_i3c_master_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
int nxfers, enum i3c_xfer_mode mode)
{
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct svc_i3c_master *master = to_svc_i3c_master(m);
@ -1669,22 +1742,36 @@ static int svc_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
struct svc_i3c_xfer *xfer;
int ret, i;
if (mode != I3C_SDR) {
/*
* Only support data size less than FIFO SIZE when using DDR
* mode. First entry is cmd in FIFO, so actual available FIFO
* for data is SVC_I3C_FIFO_SIZE - 2 since DDR only supports
* even length.
*/
for (i = 0; i < nxfers; i++)
if (xfers[i].len > SVC_I3C_FIFO_SIZE - 2)
return -EINVAL;
}
xfer = svc_i3c_master_alloc_xfer(master, nxfers);
if (!xfer)
return -ENOMEM;
xfer->type = SVC_I3C_MCTRL_TYPE_I3C;
xfer->type = i3c_mode_to_svc_type(mode);
for (i = 0; i < nxfers; i++) {
u32 rnw_cmd = (mode == I3C_SDR) ? xfers[i].rnw : xfers[i].cmd;
bool rnw = svc_cmd_is_read(rnw_cmd, xfer->type);
struct svc_i3c_cmd *cmd = &xfer->cmds[i];
cmd->xfer = &xfers[i];
cmd->addr = master->addrs[data->index];
cmd->rnw = xfers[i].rnw;
cmd->in = xfers[i].rnw ? xfers[i].data.in : NULL;
cmd->out = xfers[i].rnw ? NULL : xfers[i].data.out;
cmd->rnw_cmd = rnw_cmd;
cmd->in = rnw ? xfers[i].data.in : NULL;
cmd->out = rnw ? NULL : xfers[i].data.out;
cmd->len = xfers[i].len;
cmd->actual_len = xfers[i].rnw ? xfers[i].len : 0;
cmd->actual_len = rnw ? xfers[i].len : 0;
cmd->continued = (i + 1) < nxfers;
}
@ -1879,7 +1966,7 @@ static const struct i3c_master_controller_ops svc_i3c_master_ops = {
.do_daa = svc_i3c_master_do_daa,
.supports_ccc_cmd = svc_i3c_master_supports_ccc_cmd,
.send_ccc_cmd = svc_i3c_master_send_ccc_cmd,
.priv_xfers = svc_i3c_master_priv_xfers,
.i3c_xfers = svc_i3c_master_i3c_xfers,
.i2c_xfers = svc_i3c_master_i2c_xfers,
.request_ibi = svc_i3c_master_request_ibi,
.free_ibi = svc_i3c_master_free_ibi,

View File

@ -99,7 +99,7 @@ struct mctp_i3c_internal_hdr {
static int mctp_i3c_read(struct mctp_i3c_device *mi)
{
struct i3c_priv_xfer xfer = { .rnw = 1, .len = mi->mrl };
struct i3c_xfer xfer = { .rnw = 1, .len = mi->mrl };
struct net_device_stats *stats = &mi->mbus->ndev->stats;
struct mctp_i3c_internal_hdr *ihdr = NULL;
struct sk_buff *skb = NULL;
@ -127,7 +127,7 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi)
/* Make sure netif_rx() is read in the same order as i3c. */
mutex_lock(&mi->lock);
rc = i3c_device_do_priv_xfers(mi->i3c, &xfer, 1);
rc = i3c_device_do_xfers(mi->i3c, &xfer, 1, I3C_SDR);
if (rc < 0)
goto err;
@ -360,7 +360,7 @@ mctp_i3c_lookup(struct mctp_i3c_bus *mbus, u64 pid)
static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb)
{
struct net_device_stats *stats = &mbus->ndev->stats;
struct i3c_priv_xfer xfer = { .rnw = false };
struct i3c_xfer xfer = { .rnw = false };
struct mctp_i3c_internal_hdr *ihdr = NULL;
struct mctp_i3c_device *mi = NULL;
unsigned int data_len;
@ -409,7 +409,7 @@ static void mctp_i3c_xmit(struct mctp_i3c_bus *mbus, struct sk_buff *skb)
data[data_len] = pec;
xfer.data.out = data;
rc = i3c_device_do_priv_xfers(mi->i3c, &xfer, 1);
rc = i3c_device_do_xfers(mi->i3c, &xfer, 1, I3C_SDR);
if (rc == 0) {
stats->tx_bytes += data_len;
stats->tx_packets++;

View File

@ -27,7 +27,7 @@
* These are the standard error codes as defined by the I3C specification.
* When -EIO is returned by the i3c_device_do_priv_xfers() or
* i3c_device_send_hdr_cmds() one can check the error code in
* &struct_i3c_priv_xfer.err or &struct i3c_hdr_cmd.err to get a better idea of
* &struct_i3c_xfer.err or &struct i3c_hdr_cmd.err to get a better idea of
* what went wrong.
*
*/
@ -39,20 +39,25 @@ enum i3c_error_code {
};
/**
* enum i3c_hdr_mode - HDR mode ids
* enum i3c_xfer_mode - I3C xfer mode ids
* @I3C_HDR_DDR: DDR mode
* @I3C_HDR_TSP: TSP mode
* @I3C_HDR_TSL: TSL mode
* @I3C_SDR: SDR mode (NOT HDR mode)
*/
enum i3c_hdr_mode {
I3C_HDR_DDR,
I3C_HDR_TSP,
I3C_HDR_TSL,
enum i3c_xfer_mode {
/* The below 3 value (I3C_HDR*) must match GETCAP1 Byte bit position */
I3C_HDR_DDR = 0,
I3C_HDR_TSP = 1,
I3C_HDR_TSL = 2,
/* Use for default SDR transfer mode */
I3C_SDR = 31,
};
/**
* struct i3c_priv_xfer - I3C SDR private transfer
* struct i3c_xfer - I3C data transfer
* @rnw: encodes the transfer direction. true for a read, false for a write
* @cmd: Read/Write command in HDR mode, read: 0x80 - 0xff, write: 0x00 - 0x7f
* @len: transfer length in bytes of the transfer
* @actual_len: actual length in bytes are transferred by the controller
* @data: input/output buffer
@ -60,8 +65,11 @@ enum i3c_hdr_mode {
* @data.out: output buffer. Must point to a DMA-able buffer
* @err: I3C error code
*/
struct i3c_priv_xfer {
u8 rnw;
struct i3c_xfer {
union {
u8 rnw;
u8 cmd;
};
u16 len;
u16 actual_len;
union {
@ -71,6 +79,9 @@ struct i3c_priv_xfer {
enum i3c_error_code err;
};
/* keep back compatible */
#define i3c_priv_xfer i3c_xfer
/**
* enum i3c_dcr - I3C DCR values
* @I3C_DCR_GENERIC_DEVICE: generic I3C device
@ -297,9 +308,15 @@ static __always_inline void i3c_i2c_driver_unregister(struct i3c_driver *i3cdrv,
i3c_i2c_driver_unregister, \
__i2cdrv)
int i3c_device_do_priv_xfers(struct i3c_device *dev,
struct i3c_priv_xfer *xfers,
int nxfers);
int i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers,
int nxfers, enum i3c_xfer_mode mode);
static inline int i3c_device_do_priv_xfers(struct i3c_device *dev,
struct i3c_xfer *xfers,
int nxfers)
{
return i3c_device_do_xfers(dev, xfers, nxfers, I3C_SDR);
}
int i3c_device_do_setdasa(struct i3c_device *dev);
@ -341,5 +358,6 @@ int i3c_device_request_ibi(struct i3c_device *dev,
void i3c_device_free_ibi(struct i3c_device *dev);
int i3c_device_enable_ibi(struct i3c_device *dev);
int i3c_device_disable_ibi(struct i3c_device *dev);
u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev);
#endif /* I3C_DEV_H */

View File

@ -418,7 +418,11 @@ struct i3c_bus {
* @send_ccc_cmd: send a CCC command
* This method is mandatory.
* @priv_xfers: do one or several private I3C SDR transfers
* This method is mandatory.
* This method is mandatory when i3c_xfers is not implemented. It
* is deprecated.
* @i3c_xfers: do one or several I3C SDR or HDR transfers
* This method is mandatory when priv_xfers is not implemented but
* should be implemented instead of priv_xfers.
* @attach_i2c_dev: called every time an I2C device is attached to the bus.
* This is a good place to attach master controller specific
* data to I2C devices.
@ -474,9 +478,13 @@ struct i3c_master_controller_ops {
const struct i3c_ccc_cmd *cmd);
int (*send_ccc_cmd)(struct i3c_master_controller *master,
struct i3c_ccc_cmd *cmd);
/* Deprecated, please use i3c_xfers() */
int (*priv_xfers)(struct i3c_dev_desc *dev,
struct i3c_priv_xfer *xfers,
int nxfers);
int (*i3c_xfers)(struct i3c_dev_desc *dev,
struct i3c_xfer *xfers,
int nxfers, enum i3c_xfer_mode mode);
int (*attach_i2c_dev)(struct i2c_dev_desc *dev);
void (*detach_i2c_dev)(struct i2c_dev_desc *dev);
int (*i2c_xfers)(struct i2c_dev_desc *dev,