summaryrefslogtreecommitdiff
path: root/drivers/i3c/master/mipi-i3c-hci
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i3c/master/mipi-i3c-hci')
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c223
1 files changed, 184 insertions, 39 deletions
diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c
index 08e6cbdf89ce..dc8ede0f8ad8 100644
--- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c
+++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c
@@ -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 {
+ struct pci_dev *pci;
+ struct platform_device *pdev;
+ const struct mipi_i3c_hci_pci_info *info;
+ void *private;
+};
struct mipi_i3c_hci_pci_info {
- int (*init)(struct pci_dev *pci);
+ int (*init)(struct mipi_i3c_hci_pci *hci);
+ void (*exit)(struct mipi_i3c_hci_pci *hci);
};
+static DEFINE_IDA(mipi_i3c_hci_pci_ida);
+
#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)
+#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)
-static DEFINE_IDA(mipi_i3c_hci_pci_ida);
+#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 int mipi_i3c_hci_pci_intel_init(struct pci_dev *pci)
+static void intel_cache_ltr(struct intel_host *host)
{
- unsigned long timeout;
- void __iomem *priv;
+ host->active_ltr = readl(host->priv + INTEL_ACTIVELTR);
+ host->idle_ltr = readl(host->priv + INTEL_IDLELTR);
+}
- priv = devm_ioremap(&pci->dev,
- pci_resource_start(pci, 0) + INTEL_PRIV_OFFSET,
- INTEL_PRIV_SIZE);
- if (!priv)
- return -ENOMEM;
+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;
- /* 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();
+ /*
+ * 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;
+ }
}
- writel(INTEL_PRIV_RESETS_RESET, priv + INTEL_PRIV_RESETS);
+
+ 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 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);