mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 03:24:45 +01:00
I3C for 6.20
Subsystem:
- add sysfs entry and attribute for Device NACK Retry count
Drivers:
- dw: Device NACK Retry configuration knob
- mipi-i3c-hci: support for Multi-Bus Instances, Runtime PM support, System
Suspend support
- renesas: suspend/resume support
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEBqsFVZXh8s/0O5JiY6TcMGxwOjIFAmmChesACgkQY6TcMGxw
OjIIgg/8CXIGljGhD2UwxuIwf31Py54XmXVxGip7PygfkuJMlxe27JwkPvuQ9TIF
OXB342TpovAxfbEGphalRRXcYf95nLbmqhvyjt+h8ndJlfJeYV+iDfTxhSgr4bY/
hxMTog4OmWi8rAskp0sR0OEY1fOE3Nggx+s7W62fnuGsNzsRg8ABXp2nhJ1Q7ZxV
ZZUcnRtCLq6zn+Xlv13vMRaTIRkiPwnqdXGbra4MMYfe2QijQKKaoelt1zW14ioO
VgAQudxVU+ZH6t/NRJ7oqAN19HNqH8sJ0tOo5IDO8eDq0H36/n9v+AK0rUoEZ77q
Tyt7lksv/fxauIsaW8Zs6iqpT43CkwXhSn2t6yPT8aEczqi7L+4fpenqFYyHbw5k
i9iGMfAgxyxVSklokS9PABBfGlOK5lr0PX1lHEGpdMAeLWv0pj0FQ/15f5vzZusk
Pfd8nU2cFuNx5Zenyd5/OSSeqo/nvuF07xrWRvh8tDw45S/D7el7meJc9rQr0pO1
LVriRVCP17Tu0sC36GKXI1x1y/uoKnREzU2wogLgZaWdynXK2MMNTJY6aWUhchco
rIUQpnTW54iQkCimwzR+uUAXexx2rUkfLROTbAhgkpbaRmSYabRWy/cdDxr/Y6nr
cqrZ0QWKP203T2McpYAGSjvYLCk+Oan30GMesDOgWpASk8yqUKU=
=SukE
-----END PGP SIGNATURE-----
Merge tag 'i3c/for-6.20' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull i3c updates from Alexandre Belloni:
"Subsystem:
- add sysfs entry and attribute for Device NACK Retry count
Drivers:
- dw: Device NACK Retry configuration knob
- mipi-i3c-hci: support multi-bus instances, runtime PM, and suspend
- renesas: suspend/resume support"
* tag 'i3c/for-6.20' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (52 commits)
i3c: dw-i3c-master: fix SIR reject bit mapping for dynamic addresses
i3c: dw-i3c-master: convert spinlock usage to scoped guards
i3c: dw: Fix memory leak in dw_i3c_master_i2c_xfers()
i3c: mipi-i3c-hci-pci: Add System Suspend support
i3c: mipi-i3c-hci: Add optional System Suspend support
i3c: master: Add i3c_master_do_daa_ext() for post-hibernation address recovery
i3c: dw: Initialize spinlock to avoid upsetting lockdep
i3c: mipi-i3c-hci-pci: Add Runtime PM support
i3c: mipi-i3c-hci: Add optional Runtime PM support
i3c: master: Introduce optional Runtime PM support
i3c: mipi-i3c-hci: Factor out master dynamic address setting into helper
i3c: mipi-i3c-hci: Allow core re-initialization for Runtime PM support
i3c: mipi-i3c-hci: Factor out core initialization into helper
i3c: mipi-i3c-hci: Factor out IO mode setting into helper
i3c: mipi-i3c-hci: Factor out software reset into helper
i3c: mipi-i3c-hci: Add PIO suspend and resume support
i3c: mipi-i3c-hci: Refactor PIO register initialization
i3c: mipi-i3c-hci: Add DMA suspend and resume support
i3c: mipi-i3c-hci: Extract ring initialization from hci_dma_init()
i3c: mipi-i3c-hci: Introduce helper to restore DAT
...
This commit is contained in:
commit
2f81bdbdb3
22 changed files with 1269 additions and 514 deletions
|
|
@ -161,3 +161,14 @@ Contact: linux-i3c@vger.kernel.org
|
|||
Description:
|
||||
These directories are just symbolic links to
|
||||
/sys/bus/i3c/devices/i3c-<bus-id>/<bus-id>-<device-pid>.
|
||||
|
||||
What: /sys/bus/i3c/devices/i3c-<bus-id>/<bus-id>-<device-pid>/dev_nack_retry_count
|
||||
KernelVersion: 6.18
|
||||
Contact: linux-i3c@vger.kernel.org
|
||||
Description:
|
||||
Expose the dev_nak_retry_count which controls the number of
|
||||
automatic retries that will be performed by the controller when
|
||||
the target device returns a NACK response. A value of 0 disables
|
||||
the automatic retries. Exist only when I3C constroller supports
|
||||
this retry on nack feature.
|
||||
|
||||
|
|
|
|||
|
|
@ -46,10 +46,16 @@ int i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = i3c_bus_rpm_get(dev->bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
ret = i3c_dev_do_xfers_locked(dev->desc, xfers, nxfers, mode);
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
|
||||
i3c_bus_rpm_put(dev->bus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_do_xfers);
|
||||
|
|
@ -66,10 +72,16 @@ int i3c_device_do_setdasa(struct i3c_device *dev)
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = i3c_bus_rpm_get(dev->bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
ret = i3c_dev_setdasa_locked(dev->desc);
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
|
||||
i3c_bus_rpm_put(dev->bus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_do_setdasa);
|
||||
|
|
@ -106,16 +118,27 @@ EXPORT_SYMBOL_GPL(i3c_device_get_info);
|
|||
*/
|
||||
int i3c_device_disable_ibi(struct i3c_device *dev)
|
||||
{
|
||||
int ret = -ENOENT;
|
||||
int ret;
|
||||
|
||||
if (i3c_bus_rpm_ibi_allowed(dev->bus)) {
|
||||
ret = i3c_bus_rpm_get(dev->bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
if (dev->desc) {
|
||||
mutex_lock(&dev->desc->ibi_lock);
|
||||
ret = i3c_dev_disable_ibi_locked(dev->desc);
|
||||
mutex_unlock(&dev->desc->ibi_lock);
|
||||
} else {
|
||||
ret = -ENOENT;
|
||||
}
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
|
||||
if (!ret || i3c_bus_rpm_ibi_allowed(dev->bus))
|
||||
i3c_bus_rpm_put(dev->bus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_disable_ibi);
|
||||
|
|
@ -135,16 +158,25 @@ EXPORT_SYMBOL_GPL(i3c_device_disable_ibi);
|
|||
*/
|
||||
int i3c_device_enable_ibi(struct i3c_device *dev)
|
||||
{
|
||||
int ret = -ENOENT;
|
||||
int ret;
|
||||
|
||||
ret = i3c_bus_rpm_get(dev->bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
if (dev->desc) {
|
||||
mutex_lock(&dev->desc->ibi_lock);
|
||||
ret = i3c_dev_enable_ibi_locked(dev->desc);
|
||||
mutex_unlock(&dev->desc->ibi_lock);
|
||||
} else {
|
||||
ret = -ENOENT;
|
||||
}
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
|
||||
if (ret || i3c_bus_rpm_ibi_allowed(dev->bus))
|
||||
i3c_bus_rpm_put(dev->bus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_enable_ibi);
|
||||
|
|
@ -163,19 +195,27 @@ EXPORT_SYMBOL_GPL(i3c_device_enable_ibi);
|
|||
int i3c_device_request_ibi(struct i3c_device *dev,
|
||||
const struct i3c_ibi_setup *req)
|
||||
{
|
||||
int ret = -ENOENT;
|
||||
int ret;
|
||||
|
||||
if (!req->handler || !req->num_slots)
|
||||
return -EINVAL;
|
||||
|
||||
ret = i3c_bus_rpm_get(dev->bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_normaluse_lock(dev->bus);
|
||||
if (dev->desc) {
|
||||
mutex_lock(&dev->desc->ibi_lock);
|
||||
ret = i3c_dev_request_ibi_locked(dev->desc, req);
|
||||
mutex_unlock(&dev->desc->ibi_lock);
|
||||
} else {
|
||||
ret = -ENOENT;
|
||||
}
|
||||
i3c_bus_normaluse_unlock(dev->bus);
|
||||
|
||||
i3c_bus_rpm_put(dev->bus);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_device_request_ibi);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@
|
|||
#include <linux/i3c/master.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
int __must_check i3c_bus_rpm_get(struct i3c_bus *bus);
|
||||
void i3c_bus_rpm_put(struct i3c_bus *bus);
|
||||
bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus);
|
||||
|
||||
void i3c_bus_normaluse_lock(struct i3c_bus *bus);
|
||||
void i3c_bus_normaluse_unlock(struct i3c_bus *bus);
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,38 @@ static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev)
|
|||
return container_of(dev, struct i3c_master_controller, dev);
|
||||
}
|
||||
|
||||
static int __must_check i3c_master_rpm_get(struct i3c_master_controller *master)
|
||||
{
|
||||
int ret = master->rpm_allowed ? pm_runtime_resume_and_get(master->dev.parent) : 0;
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(master->dev.parent, "runtime resume failed, error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void i3c_master_rpm_put(struct i3c_master_controller *master)
|
||||
{
|
||||
if (master->rpm_allowed)
|
||||
pm_runtime_put_autosuspend(master->dev.parent);
|
||||
}
|
||||
|
||||
int i3c_bus_rpm_get(struct i3c_bus *bus)
|
||||
{
|
||||
return i3c_master_rpm_get(i3c_bus_to_i3c_master(bus));
|
||||
}
|
||||
|
||||
void i3c_bus_rpm_put(struct i3c_bus *bus)
|
||||
{
|
||||
i3c_master_rpm_put(i3c_bus_to_i3c_master(bus));
|
||||
}
|
||||
|
||||
bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus)
|
||||
{
|
||||
return i3c_bus_to_i3c_master(bus)->rpm_ibi_allowed;
|
||||
}
|
||||
|
||||
static const struct device_type i3c_device_type;
|
||||
|
||||
static struct i3c_bus *dev_to_i3cbus(struct device *dev)
|
||||
|
|
@ -611,6 +643,12 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
|
|||
if (!master->ops->enable_hotjoin || !master->ops->disable_hotjoin)
|
||||
return -EINVAL;
|
||||
|
||||
if (enable || master->rpm_ibi_allowed) {
|
||||
ret = i3c_master_rpm_get(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
i3c_bus_normaluse_lock(&master->bus);
|
||||
|
||||
if (enable)
|
||||
|
|
@ -618,10 +656,14 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
|
|||
else
|
||||
ret = master->ops->disable_hotjoin(master);
|
||||
|
||||
master->hotjoin = enable;
|
||||
if (!ret)
|
||||
master->hotjoin = enable;
|
||||
|
||||
i3c_bus_normaluse_unlock(&master->bus);
|
||||
|
||||
if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
|
||||
i3c_master_rpm_put(master);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -683,6 +725,39 @@ static ssize_t hotjoin_show(struct device *dev, struct device_attribute *da, cha
|
|||
|
||||
static DEVICE_ATTR_RW(hotjoin);
|
||||
|
||||
static ssize_t dev_nack_retry_count_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%u\n", dev_to_i3cmaster(dev)->dev_nack_retry_count);
|
||||
}
|
||||
|
||||
static ssize_t dev_nack_retry_count_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
|
||||
struct i3c_master_controller *master = dev_to_i3cmaster(dev);
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_maintenance_lock(i3cbus);
|
||||
ret = master->ops->set_dev_nack_retry(master, val);
|
||||
i3c_bus_maintenance_unlock(i3cbus);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
master->dev_nack_retry_count = val;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(dev_nack_retry_count);
|
||||
|
||||
static struct attribute *i3c_masterdev_attrs[] = {
|
||||
&dev_attr_mode.attr,
|
||||
&dev_attr_current_master.attr,
|
||||
|
|
@ -1692,37 +1767,69 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version)
|
||||
* @master: controller
|
||||
* @rstdaa: whether to first perform Reset of Dynamic Addresses (RSTDAA)
|
||||
*
|
||||
* Perform Dynamic Address Assignment with optional support for System
|
||||
* Hibernation (@rstdaa is true).
|
||||
*
|
||||
* After System Hibernation, Dynamic Addresses can have been reassigned at boot
|
||||
* time to different values. A simple strategy is followed to handle that.
|
||||
* Perform a Reset of Dynamic Addresses (RSTDAA) followed by the normal DAA
|
||||
* procedure which has provision for reassigning addresses that differ from the
|
||||
* previously recorded addresses.
|
||||
*
|
||||
* Return: a 0 in case of success, an negative error code otherwise.
|
||||
*/
|
||||
int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa)
|
||||
{
|
||||
int rstret = 0;
|
||||
int ret;
|
||||
|
||||
ret = i3c_master_rpm_get(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_maintenance_lock(&master->bus);
|
||||
|
||||
if (rstdaa) {
|
||||
rstret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
|
||||
if (rstret == I3C_ERROR_M2)
|
||||
rstret = 0;
|
||||
}
|
||||
|
||||
ret = master->ops->do_daa(master);
|
||||
|
||||
i3c_bus_maintenance_unlock(&master->bus);
|
||||
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
i3c_bus_normaluse_lock(&master->bus);
|
||||
i3c_master_register_new_i3c_devs(master);
|
||||
i3c_bus_normaluse_unlock(&master->bus);
|
||||
out:
|
||||
i3c_master_rpm_put(master);
|
||||
|
||||
return rstret ?: ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_do_daa_ext);
|
||||
|
||||
/**
|
||||
* i3c_master_do_daa() - do a DAA (Dynamic Address Assignment)
|
||||
* @master: master doing the DAA
|
||||
*
|
||||
* This function is instantiating an I3C device object and adding it to the
|
||||
* I3C device list. All device information are automatically retrieved using
|
||||
* This function instantiates I3C device objects and adds them to the
|
||||
* I3C device list. All device information is automatically retrieved using
|
||||
* standard CCC commands.
|
||||
*
|
||||
* The I3C device object is returned in case the master wants to attach
|
||||
* private data to it using i3c_dev_set_master_data().
|
||||
*
|
||||
* This function must be called with the bus lock held in write mode.
|
||||
*
|
||||
* Return: a 0 in case of success, an negative error code otherwise.
|
||||
*/
|
||||
int i3c_master_do_daa(struct i3c_master_controller *master)
|
||||
{
|
||||
int ret;
|
||||
|
||||
i3c_bus_maintenance_lock(&master->bus);
|
||||
ret = master->ops->do_daa(master);
|
||||
i3c_bus_maintenance_unlock(&master->bus);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_normaluse_lock(&master->bus);
|
||||
i3c_master_register_new_i3c_devs(master);
|
||||
i3c_bus_normaluse_unlock(&master->bus);
|
||||
|
||||
return 0;
|
||||
return i3c_master_do_daa_ext(master, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i3c_master_do_daa);
|
||||
|
||||
|
|
@ -2064,8 +2171,17 @@ err_detach_devs:
|
|||
|
||||
static void i3c_master_bus_cleanup(struct i3c_master_controller *master)
|
||||
{
|
||||
if (master->ops->bus_cleanup)
|
||||
master->ops->bus_cleanup(master);
|
||||
if (master->ops->bus_cleanup) {
|
||||
int ret = i3c_master_rpm_get(master);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&master->dev,
|
||||
"runtime resume error: master bus_cleanup() not done\n");
|
||||
} else {
|
||||
master->ops->bus_cleanup(master);
|
||||
i3c_master_rpm_put(master);
|
||||
}
|
||||
}
|
||||
|
||||
i3c_master_detach_free_devs(master);
|
||||
}
|
||||
|
|
@ -2370,19 +2486,16 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master)
|
|||
{
|
||||
struct device *dev = &master->dev;
|
||||
struct device_node *i3cbus_np = dev->of_node;
|
||||
struct device_node *node;
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (!i3cbus_np)
|
||||
return 0;
|
||||
|
||||
for_each_available_child_of_node(i3cbus_np, node) {
|
||||
for_each_available_child_of_node_scoped(i3cbus_np, node) {
|
||||
ret = of_i3c_master_add_dev(master, node);
|
||||
if (ret) {
|
||||
of_node_put(node);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -2420,6 +2533,10 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap,
|
|||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = i3c_master_rpm_get(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_normaluse_lock(&master->bus);
|
||||
dev = i3c_master_find_i2c_dev_by_addr(master, addr);
|
||||
if (!dev)
|
||||
|
|
@ -2428,6 +2545,8 @@ static int i3c_master_i2c_adapter_xfer(struct i2c_adapter *adap,
|
|||
ret = master->ops->i2c_xfers(dev, xfers, nxfers);
|
||||
i3c_bus_normaluse_unlock(&master->bus);
|
||||
|
||||
i3c_master_rpm_put(master);
|
||||
|
||||
return ret ? ret : nxfers;
|
||||
}
|
||||
|
||||
|
|
@ -2530,6 +2649,10 @@ static int i3c_i2c_notifier_call(struct notifier_block *nb, unsigned long action
|
|||
|
||||
master = i2c_adapter_to_i3c_master(adap);
|
||||
|
||||
ret = i3c_master_rpm_get(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i3c_bus_maintenance_lock(&master->bus);
|
||||
switch (action) {
|
||||
case BUS_NOTIFY_ADD_DEVICE:
|
||||
|
|
@ -2543,6 +2666,8 @@ static int i3c_i2c_notifier_call(struct notifier_block *nb, unsigned long action
|
|||
}
|
||||
i3c_bus_maintenance_unlock(&master->bus);
|
||||
|
||||
i3c_master_rpm_put(master);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2880,8 +3005,11 @@ int i3c_master_register(struct i3c_master_controller *master,
|
|||
INIT_LIST_HEAD(&master->boardinfo.i2c);
|
||||
INIT_LIST_HEAD(&master->boardinfo.i3c);
|
||||
|
||||
ret = i3c_master_rpm_get(master);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
device_initialize(&master->dev);
|
||||
dev_set_name(&master->dev, "i3c-%d", i3cbus->id);
|
||||
|
||||
master->dev.dma_mask = parent->dma_mask;
|
||||
master->dev.coherent_dma_mask = parent->coherent_dma_mask;
|
||||
|
|
@ -2891,6 +3019,8 @@ int i3c_master_register(struct i3c_master_controller *master,
|
|||
if (ret)
|
||||
goto err_put_dev;
|
||||
|
||||
dev_set_name(&master->dev, "i3c-%d", i3cbus->id);
|
||||
|
||||
ret = of_populate_i3c_bus(master);
|
||||
if (ret)
|
||||
goto err_put_dev;
|
||||
|
|
@ -2959,6 +3089,11 @@ int i3c_master_register(struct i3c_master_controller *master,
|
|||
i3c_master_register_new_i3c_devs(master);
|
||||
i3c_bus_normaluse_unlock(&master->bus);
|
||||
|
||||
if (master->ops->set_dev_nack_retry)
|
||||
device_create_file(&master->dev, &dev_attr_dev_nack_retry_count);
|
||||
|
||||
i3c_master_rpm_put(master);
|
||||
|
||||
return 0;
|
||||
|
||||
err_del_dev:
|
||||
|
|
@ -2968,6 +3103,7 @@ err_cleanup_bus:
|
|||
i3c_master_bus_cleanup(master);
|
||||
|
||||
err_put_dev:
|
||||
i3c_master_rpm_put(master);
|
||||
put_device(&master->dev);
|
||||
|
||||
return ret;
|
||||
|
|
@ -2984,6 +3120,9 @@ void i3c_master_unregister(struct i3c_master_controller *master)
|
|||
{
|
||||
i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE);
|
||||
|
||||
if (master->ops->set_dev_nack_retry)
|
||||
device_remove_file(&master->dev, &dev_attr_dev_nack_retry_count);
|
||||
|
||||
i3c_master_i2c_adapter_cleanup(master);
|
||||
i3c_master_unregister_i3c_devs(master);
|
||||
i3c_master_bus_cleanup(master);
|
||||
|
|
@ -3112,8 +3251,18 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
|
|||
if (!dev->ibi)
|
||||
return;
|
||||
|
||||
if (WARN_ON(dev->ibi->enabled))
|
||||
WARN_ON(i3c_dev_disable_ibi_locked(dev));
|
||||
if (dev->ibi->enabled) {
|
||||
int ret;
|
||||
|
||||
dev_err(&master->dev, "Freeing IBI that is still enabled\n");
|
||||
ret = i3c_master_rpm_get(master);
|
||||
if (!ret) {
|
||||
ret = i3c_dev_disable_ibi_locked(dev);
|
||||
i3c_master_rpm_put(master);
|
||||
}
|
||||
if (ret)
|
||||
dev_err(&master->dev, "Failed to disable IBI before freeing\n");
|
||||
}
|
||||
|
||||
master->ops->free_ibi(dev);
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ 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.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* Author: Vitor Soares <vitor.soares@synopsys.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/completion.h>
|
||||
|
|
@ -204,12 +205,23 @@
|
|||
#define EXTENDED_CAPABILITY 0xe8
|
||||
#define SLAVE_CONFIG 0xec
|
||||
|
||||
#define DYN_ADDR_LO_MASK GENMASK(4, 0)
|
||||
#define DYN_ADDR_HI_MASK GENMASK(6, 5)
|
||||
#define IBI_SIR_BIT_MOD 32 /* 32-bit vector */
|
||||
|
||||
#define DW_I3C_DEV_NACK_RETRY_CNT_MAX 0x3
|
||||
#define DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK GENMASK(30, 29)
|
||||
#define DEV_ADDR_TABLE_DYNAMIC_MASK GENMASK(23, 16)
|
||||
#define DEV_ADDR_TABLE_STATIC_MASK GENMASK(6, 0)
|
||||
#define DEV_ADDR_TABLE_IBI_MDB BIT(12)
|
||||
#define DEV_ADDR_TABLE_SIR_REJECT BIT(13)
|
||||
#define DEV_ADDR_TABLE_DEV_NACK_RETRY_CNT(x) \
|
||||
FIELD_PREP(DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK, (x))
|
||||
#define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31)
|
||||
#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) (((x) << 16) & GENMASK(23, 16))
|
||||
#define DEV_ADDR_TABLE_STATIC_ADDR(x) ((x) & GENMASK(6, 0))
|
||||
#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_DYNAMIC_MASK, x)
|
||||
#define DEV_ADDR_TABLE_STATIC_ADDR(x) FIELD_PREP(DEV_ADDR_TABLE_STATIC_MASK, x)
|
||||
#define DEV_ADDR_TABLE_LOC(start, idx) ((start) + ((idx) << 2))
|
||||
#define DEV_ADDR_TABLE_GET_DYNAMIC_ADDR(x) FIELD_GET(DEV_ADDR_TABLE_DYNAMIC_MASK, x)
|
||||
|
||||
#define I3C_BUS_SDR1_SCL_RATE 8000000
|
||||
#define I3C_BUS_SDR2_SCL_RATE 6000000
|
||||
|
|
@ -257,6 +269,14 @@ struct dw_i3c_drvdata {
|
|||
u32 flags;
|
||||
};
|
||||
|
||||
static inline u32 get_ibi_sir_bit_index(u8 addr)
|
||||
{
|
||||
u32 lo = FIELD_GET(DYN_ADDR_LO_MASK, addr);
|
||||
u32 hi = FIELD_GET(DYN_ADDR_HI_MASK, addr);
|
||||
|
||||
return (lo + hi) % IBI_SIR_BIT_MOD;
|
||||
}
|
||||
|
||||
static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
|
||||
const struct i3c_ccc_cmd *cmd)
|
||||
{
|
||||
|
|
@ -409,17 +429,14 @@ static void dw_i3c_master_start_xfer_locked(struct dw_i3c_master *master)
|
|||
static void dw_i3c_master_enqueue_xfer(struct dw_i3c_master *master,
|
||||
struct dw_i3c_xfer *xfer)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
init_completion(&xfer->comp);
|
||||
spin_lock_irqsave(&master->xferqueue.lock, flags);
|
||||
guard(spinlock_irqsave)(&master->xferqueue.lock);
|
||||
if (master->xferqueue.cur) {
|
||||
list_add_tail(&xfer->node, &master->xferqueue.list);
|
||||
} else {
|
||||
master->xferqueue.cur = xfer;
|
||||
dw_i3c_master_start_xfer_locked(master);
|
||||
}
|
||||
spin_unlock_irqrestore(&master->xferqueue.lock, flags);
|
||||
}
|
||||
|
||||
static void dw_i3c_master_dequeue_xfer_locked(struct dw_i3c_master *master,
|
||||
|
|
@ -444,11 +461,8 @@ static void dw_i3c_master_dequeue_xfer_locked(struct dw_i3c_master *master,
|
|||
static void dw_i3c_master_dequeue_xfer(struct dw_i3c_master *master,
|
||||
struct dw_i3c_xfer *xfer)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&master->xferqueue.lock, flags);
|
||||
guard(spinlock_irqsave)(&master->xferqueue.lock);
|
||||
dw_i3c_master_dequeue_xfer_locked(master, xfer);
|
||||
spin_unlock_irqrestore(&master->xferqueue.lock, flags);
|
||||
}
|
||||
|
||||
static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
|
||||
|
|
@ -1099,6 +1113,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
|
|||
dev_err(master->dev,
|
||||
"<%s> cannot resume i3c bus master, err: %d\n",
|
||||
__func__, ret);
|
||||
dw_i3c_master_free_xfer(xfer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -1187,15 +1202,13 @@ static int dw_i3c_master_request_ibi(struct i3c_dev_desc *dev,
|
|||
struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct dw_i3c_master *master = to_dw_i3c_master(m);
|
||||
unsigned long flags;
|
||||
|
||||
data->ibi_pool = i3c_generic_ibi_alloc_pool(dev, req);
|
||||
if (IS_ERR(data->ibi_pool))
|
||||
return PTR_ERR(data->ibi_pool);
|
||||
|
||||
spin_lock_irqsave(&master->devs_lock, flags);
|
||||
guard(spinlock_irqsave)(&master->devs_lock);
|
||||
master->devs[data->index].ibi_dev = dev;
|
||||
spin_unlock_irqrestore(&master->devs_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1205,11 +1218,10 @@ static void dw_i3c_master_free_ibi(struct i3c_dev_desc *dev)
|
|||
struct dw_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
|
||||
struct i3c_master_controller *m = i3c_dev_get_master(dev);
|
||||
struct dw_i3c_master *master = to_dw_i3c_master(m);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&master->devs_lock, flags);
|
||||
master->devs[data->index].ibi_dev = NULL;
|
||||
spin_unlock_irqrestore(&master->devs_lock, flags);
|
||||
scoped_guard(spinlock_irqsave, &master->devs_lock) {
|
||||
master->devs[data->index].ibi_dev = NULL;
|
||||
}
|
||||
|
||||
i3c_generic_ibi_free_pool(data->ibi_pool);
|
||||
data->ibi_pool = NULL;
|
||||
|
|
@ -1236,14 +1248,21 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master,
|
|||
struct i3c_dev_desc *dev,
|
||||
u8 idx, bool enable)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 dat_entry, reg;
|
||||
bool global;
|
||||
u8 dynamic_addr;
|
||||
|
||||
dat_entry = DEV_ADDR_TABLE_LOC(master->datstartaddr, idx);
|
||||
|
||||
spin_lock_irqsave(&master->devs_lock, flags);
|
||||
guard(spinlock_irqsave)(&master->devs_lock);
|
||||
reg = readl(master->regs + dat_entry);
|
||||
dynamic_addr = DEV_ADDR_TABLE_GET_DYNAMIC_ADDR(reg);
|
||||
|
||||
if (!dynamic_addr)
|
||||
dev_warn(master->dev,
|
||||
"<%s> unassigned slave device, dynamic addr:%x\n",
|
||||
__func__, dynamic_addr);
|
||||
|
||||
if (enable) {
|
||||
reg &= ~DEV_ADDR_TABLE_SIR_REJECT;
|
||||
if (dev->info.bcr & I3C_BCR_IBI_PAYLOAD)
|
||||
|
|
@ -1256,20 +1275,17 @@ static void dw_i3c_master_set_sir_enabled(struct dw_i3c_master *master,
|
|||
|
||||
if (enable) {
|
||||
global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL);
|
||||
master->sir_rej_mask &= ~BIT(idx);
|
||||
master->sir_rej_mask &= ~BIT(get_ibi_sir_bit_index(dynamic_addr));
|
||||
} else {
|
||||
bool hj_rejected = !!(readl(master->regs + DEVICE_CTRL) & DEV_CTRL_HOT_JOIN_NACK);
|
||||
|
||||
master->sir_rej_mask |= BIT(idx);
|
||||
master->sir_rej_mask |= BIT(get_ibi_sir_bit_index(dynamic_addr));
|
||||
global = (master->sir_rej_mask == IBI_REQ_REJECT_ALL) && hj_rejected;
|
||||
}
|
||||
writel(master->sir_rej_mask, master->regs + IBI_SIR_REQ_REJECT);
|
||||
|
||||
if (global)
|
||||
dw_i3c_master_enable_sir_signal(master, enable);
|
||||
|
||||
|
||||
spin_unlock_irqrestore(&master->devs_lock, flags);
|
||||
}
|
||||
|
||||
static int dw_i3c_master_enable_hotjoin(struct i3c_master_controller *m)
|
||||
|
|
@ -1370,7 +1386,6 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
|
|||
struct dw_i3c_i2c_dev_data *data;
|
||||
struct i3c_ibi_slot *slot;
|
||||
struct i3c_dev_desc *dev;
|
||||
unsigned long flags;
|
||||
u8 addr, len;
|
||||
int idx;
|
||||
|
||||
|
|
@ -1388,7 +1403,7 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
|
|||
* a new platform op to validate it.
|
||||
*/
|
||||
|
||||
spin_lock_irqsave(&master->devs_lock, flags);
|
||||
guard(spinlock_irqsave)(&master->devs_lock);
|
||||
idx = dw_i3c_master_get_addr_pos(master, addr);
|
||||
if (idx < 0) {
|
||||
dev_dbg_ratelimited(&master->base.dev,
|
||||
|
|
@ -1424,14 +1439,10 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
|
|||
}
|
||||
i3c_master_queue_ibi(dev, slot);
|
||||
|
||||
spin_unlock_irqrestore(&master->devs_lock, flags);
|
||||
|
||||
return;
|
||||
|
||||
err_drain:
|
||||
dw_i3c_master_drain_ibi_queue(master, len);
|
||||
|
||||
spin_unlock_irqrestore(&master->devs_lock, flags);
|
||||
}
|
||||
|
||||
/* "ibis": referring to In-Band Interrupts, and not
|
||||
|
|
@ -1489,6 +1500,40 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m,
|
||||
unsigned long dev_nack_retry_cnt)
|
||||
{
|
||||
struct dw_i3c_master *master = to_dw_i3c_master(m);
|
||||
u32 reg;
|
||||
int i;
|
||||
|
||||
if (dev_nack_retry_cnt > DW_I3C_DEV_NACK_RETRY_CNT_MAX) {
|
||||
dev_err(&master->base.dev,
|
||||
"Value %ld exceeds maximum %d\n",
|
||||
dev_nack_retry_cnt, DW_I3C_DEV_NACK_RETRY_CNT_MAX);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update DAT entries for all currently attached devices.
|
||||
* We directly iterate through the master's device array.
|
||||
*/
|
||||
for (i = 0; i < master->maxdevs; i++) {
|
||||
/* Skip free/empty slots */
|
||||
if (master->free_pos & BIT(i))
|
||||
continue;
|
||||
|
||||
reg = readl(master->regs +
|
||||
DEV_ADDR_TABLE_LOC(master->datstartaddr, i));
|
||||
reg &= ~DEV_ADDR_TABLE_DEV_NACK_RETRY_MASK;
|
||||
reg |= DEV_ADDR_TABLE_DEV_NACK_RETRY_CNT(dev_nack_retry_cnt);
|
||||
writel(reg, master->regs +
|
||||
DEV_ADDR_TABLE_LOC(master->datstartaddr, i));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
|
||||
.bus_init = dw_i3c_master_bus_init,
|
||||
.bus_cleanup = dw_i3c_master_bus_cleanup,
|
||||
|
|
@ -1509,6 +1554,7 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = {
|
|||
.recycle_ibi_slot = dw_i3c_master_recycle_ibi_slot,
|
||||
.enable_hotjoin = dw_i3c_master_enable_hotjoin,
|
||||
.disable_hotjoin = dw_i3c_master_disable_hotjoin,
|
||||
.set_dev_nack_retry = dw_i3c_master_set_dev_nack_retry,
|
||||
};
|
||||
|
||||
/* default platform ops implementations */
|
||||
|
|
@ -1570,6 +1616,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
|
|||
spin_lock_init(&master->xferqueue.lock);
|
||||
INIT_LIST_HEAD(&master->xferqueue.list);
|
||||
|
||||
spin_lock_init(&master->devs_lock);
|
||||
|
||||
writel(INTR_ALL, master->regs + INTR_STATUS);
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
ret = devm_request_irq(&pdev->dev, irq,
|
||||
|
|
@ -1676,11 +1724,16 @@ static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master)
|
|||
if (master->free_pos & BIT(pos))
|
||||
continue;
|
||||
|
||||
if (master->devs[pos].is_i2c_addr)
|
||||
reg_val = DEV_ADDR_TABLE_LEGACY_I2C_DEV |
|
||||
reg_val = readl(master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
|
||||
|
||||
if (master->devs[pos].is_i2c_addr) {
|
||||
reg_val &= ~DEV_ADDR_TABLE_STATIC_MASK;
|
||||
reg_val |= DEV_ADDR_TABLE_LEGACY_I2C_DEV |
|
||||
DEV_ADDR_TABLE_STATIC_ADDR(master->devs[pos].addr);
|
||||
else
|
||||
reg_val = DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr);
|
||||
} else {
|
||||
reg_val &= ~DEV_ADDR_TABLE_DYNAMIC_MASK;
|
||||
reg_val |= DEV_ADDR_TABLE_DYNAMIC_ADDR(master->devs[pos].addr);
|
||||
}
|
||||
|
||||
writel(reg_val, master->regs + DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#include "dat.h"
|
||||
#include "dct.h"
|
||||
|
||||
|
||||
/*
|
||||
* Address Assignment Command
|
||||
*/
|
||||
|
|
@ -100,7 +99,6 @@
|
|||
#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,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
#include "cmd.h"
|
||||
#include "xfer_mode_rate.h"
|
||||
|
||||
|
||||
/*
|
||||
* Unified Data Transfer Command
|
||||
*/
|
||||
|
|
@ -62,7 +61,6 @@
|
|||
#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);
|
||||
|
|
|
|||
|
|
@ -14,14 +14,15 @@
|
|||
#include <linux/interrupt.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/mipi-i3c-hci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "hci.h"
|
||||
#include "ext_caps.h"
|
||||
#include "cmd.h"
|
||||
#include "dat.h"
|
||||
|
||||
|
||||
/*
|
||||
* Host Controller Capabilities and Operation Registers
|
||||
*/
|
||||
|
|
@ -109,12 +110,17 @@
|
|||
#define DEV_CTX_BASE_LO 0x60
|
||||
#define DEV_CTX_BASE_HI 0x64
|
||||
|
||||
|
||||
static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m)
|
||||
{
|
||||
return container_of(m, struct i3c_hci, master);
|
||||
}
|
||||
|
||||
static void i3c_hci_set_master_dyn_addr(struct i3c_hci *hci)
|
||||
{
|
||||
reg_write(MASTER_DEVICE_ADDR,
|
||||
MASTER_DYNAMIC_ADDR(hci->dyn_addr) | MASTER_DYNAMIC_ADDR_VALID);
|
||||
}
|
||||
|
||||
static int i3c_hci_bus_init(struct i3c_master_controller *m)
|
||||
{
|
||||
struct i3c_hci *hci = to_i3c_hci(m);
|
||||
|
|
@ -130,10 +136,10 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m)
|
|||
ret = i3c_master_get_free_addr(m, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
reg_write(MASTER_DEVICE_ADDR,
|
||||
MASTER_DYNAMIC_ADDR(ret) | MASTER_DYNAMIC_ADDR_VALID);
|
||||
hci->dyn_addr = ret;
|
||||
i3c_hci_set_master_dyn_addr(hci);
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.dyn_addr = ret;
|
||||
info.dyn_addr = hci->dyn_addr;
|
||||
ret = i3c_master_set_info(m, &info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -152,16 +158,41 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Bus disable should never fail, so be generous with the timeout */
|
||||
#define BUS_DISABLE_TIMEOUT_US (500 * USEC_PER_MSEC)
|
||||
|
||||
static int i3c_hci_bus_disable(struct i3c_hci *hci)
|
||||
{
|
||||
u32 regval;
|
||||
int ret;
|
||||
|
||||
reg_clear(HC_CONTROL, HC_CONTROL_BUS_ENABLE);
|
||||
|
||||
/* Ensure controller is disabled */
|
||||
ret = readx_poll_timeout(reg_read, HC_CONTROL, regval,
|
||||
!(regval & HC_CONTROL_BUS_ENABLE), 0, BUS_DISABLE_TIMEOUT_US);
|
||||
if (ret)
|
||||
dev_err(&hci->master.dev, "%s: Failed to disable bus\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void i3c_hci_sync_irq_inactive(struct i3c_hci *hci)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(hci->master.dev.parent);
|
||||
int irq = platform_get_irq(pdev, 0);
|
||||
|
||||
reg_write(INTR_SIGNAL_ENABLE, 0x0);
|
||||
hci->irq_inactive = true;
|
||||
synchronize_irq(irq);
|
||||
}
|
||||
|
||||
static void i3c_hci_bus_cleanup(struct i3c_master_controller *m)
|
||||
{
|
||||
struct i3c_hci *hci = to_i3c_hci(m);
|
||||
struct platform_device *pdev = to_platform_device(m->dev.parent);
|
||||
|
||||
reg_clear(HC_CONTROL, HC_CONTROL_BUS_ENABLE);
|
||||
synchronize_irq(platform_get_irq(pdev, 0));
|
||||
i3c_hci_bus_disable(hci);
|
||||
hci->io->cleanup(hci);
|
||||
if (hci->cmd == &mipi_i3c_hci_cmd_v1)
|
||||
mipi_i3c_hci_dat_v1.cleanup(hci);
|
||||
}
|
||||
|
||||
void mipi_i3c_hci_resume(struct i3c_hci *hci)
|
||||
|
|
@ -535,6 +566,14 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
|
|||
irqreturn_t result = IRQ_NONE;
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* The IRQ can be shared, so the handler may be called when the IRQ is
|
||||
* due to a different device. That could happen when runtime suspended,
|
||||
* so exit immediately if IRQs are not expected for this device.
|
||||
*/
|
||||
if (hci->irq_inactive)
|
||||
return IRQ_NONE;
|
||||
|
||||
val = reg_read(INTR_STATUS);
|
||||
reg_write(INTR_STATUS, val);
|
||||
dev_dbg(&hci->master.dev, "INTR_STATUS %#x", val);
|
||||
|
|
@ -562,87 +601,66 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
|
|||
return result;
|
||||
}
|
||||
|
||||
static int i3c_hci_init(struct i3c_hci *hci)
|
||||
static int i3c_hci_software_reset(struct i3c_hci *hci)
|
||||
{
|
||||
bool size_in_dwords, mode_selector;
|
||||
u32 regval, offset;
|
||||
u32 regval;
|
||||
int ret;
|
||||
|
||||
/* Validate HCI hardware version */
|
||||
regval = reg_read(HCI_VERSION);
|
||||
hci->version_major = (regval >> 8) & 0xf;
|
||||
hci->version_minor = (regval >> 4) & 0xf;
|
||||
hci->revision = regval & 0xf;
|
||||
dev_notice(&hci->master.dev, "MIPI I3C HCI v%u.%u r%02u\n",
|
||||
hci->version_major, hci->version_minor, hci->revision);
|
||||
/* known versions */
|
||||
switch (regval & ~0xf) {
|
||||
case 0x100: /* version 1.0 */
|
||||
case 0x110: /* version 1.1 */
|
||||
case 0x200: /* version 2.0 */
|
||||
break;
|
||||
default:
|
||||
dev_err(&hci->master.dev, "unsupported HCI version\n");
|
||||
return -EPROTONOSUPPORT;
|
||||
}
|
||||
|
||||
hci->caps = reg_read(HC_CAPABILITIES);
|
||||
dev_dbg(&hci->master.dev, "caps = %#x", hci->caps);
|
||||
|
||||
size_in_dwords = hci->version_major < 1 ||
|
||||
(hci->version_major == 1 && hci->version_minor < 1);
|
||||
|
||||
regval = reg_read(DAT_SECTION);
|
||||
offset = FIELD_GET(DAT_TABLE_OFFSET, regval);
|
||||
hci->DAT_regs = offset ? hci->base_regs + offset : NULL;
|
||||
hci->DAT_entries = FIELD_GET(DAT_TABLE_SIZE, regval);
|
||||
hci->DAT_entry_size = FIELD_GET(DAT_ENTRY_SIZE, regval) ? 0 : 8;
|
||||
if (size_in_dwords)
|
||||
hci->DAT_entries = 4 * hci->DAT_entries / hci->DAT_entry_size;
|
||||
dev_info(&hci->master.dev, "DAT: %u %u-bytes entries at offset %#x\n",
|
||||
hci->DAT_entries, hci->DAT_entry_size, offset);
|
||||
|
||||
regval = reg_read(DCT_SECTION);
|
||||
offset = FIELD_GET(DCT_TABLE_OFFSET, regval);
|
||||
hci->DCT_regs = offset ? hci->base_regs + offset : NULL;
|
||||
hci->DCT_entries = FIELD_GET(DCT_TABLE_SIZE, regval);
|
||||
hci->DCT_entry_size = FIELD_GET(DCT_ENTRY_SIZE, regval) ? 0 : 16;
|
||||
if (size_in_dwords)
|
||||
hci->DCT_entries = 4 * hci->DCT_entries / hci->DCT_entry_size;
|
||||
dev_info(&hci->master.dev, "DCT: %u %u-bytes entries at offset %#x\n",
|
||||
hci->DCT_entries, hci->DCT_entry_size, offset);
|
||||
|
||||
regval = reg_read(RING_HEADERS_SECTION);
|
||||
offset = FIELD_GET(RING_HEADERS_OFFSET, regval);
|
||||
hci->RHS_regs = offset ? hci->base_regs + offset : NULL;
|
||||
dev_info(&hci->master.dev, "Ring Headers at offset %#x\n", offset);
|
||||
|
||||
regval = reg_read(PIO_SECTION);
|
||||
offset = FIELD_GET(PIO_REGS_OFFSET, regval);
|
||||
hci->PIO_regs = offset ? hci->base_regs + offset : NULL;
|
||||
dev_info(&hci->master.dev, "PIO section at offset %#x\n", offset);
|
||||
|
||||
regval = reg_read(EXT_CAPS_SECTION);
|
||||
offset = FIELD_GET(EXT_CAPS_OFFSET, regval);
|
||||
hci->EXTCAPS_regs = offset ? hci->base_regs + offset : NULL;
|
||||
dev_info(&hci->master.dev, "Extended Caps at offset %#x\n", offset);
|
||||
|
||||
ret = i3c_hci_parse_ext_caps(hci);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Now let's reset the hardware.
|
||||
* SOFT_RST must be clear before we write to it.
|
||||
* Then we must wait until it clears again.
|
||||
*/
|
||||
ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval,
|
||||
!(regval & SOFT_RST), 1, 10000);
|
||||
if (ret)
|
||||
return -ENXIO;
|
||||
!(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC);
|
||||
if (ret) {
|
||||
dev_err(&hci->master.dev, "%s: Software reset stuck\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
reg_write(RESET_CONTROL, SOFT_RST);
|
||||
|
||||
ret = readx_poll_timeout(reg_read, RESET_CONTROL, regval,
|
||||
!(regval & SOFT_RST), 1, 10000);
|
||||
!(regval & SOFT_RST), 0, 10 * USEC_PER_MSEC);
|
||||
if (ret) {
|
||||
dev_err(&hci->master.dev, "%s: Software reset failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool is_version_1_1_or_newer(struct i3c_hci *hci)
|
||||
{
|
||||
return hci->version_major > 1 || (hci->version_major == 1 && hci->version_minor > 0);
|
||||
}
|
||||
|
||||
static int i3c_hci_set_io_mode(struct i3c_hci *hci, bool dma)
|
||||
{
|
||||
bool pio_mode;
|
||||
|
||||
if (dma)
|
||||
reg_clear(HC_CONTROL, HC_CONTROL_PIO_MODE);
|
||||
else
|
||||
reg_set(HC_CONTROL, HC_CONTROL_PIO_MODE);
|
||||
|
||||
if (!is_version_1_1_or_newer(hci))
|
||||
return 0;
|
||||
|
||||
pio_mode = reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE;
|
||||
if ((dma && pio_mode) || (!dma && !pio_mode)) {
|
||||
dev_err(&hci->master.dev, "%s mode is stuck\n", pio_mode ? "PIO" : "DMA");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i3c_hci_reset_and_init(struct i3c_hci *hci)
|
||||
{
|
||||
u32 regval;
|
||||
int ret;
|
||||
|
||||
ret = i3c_hci_software_reset(hci);
|
||||
if (ret)
|
||||
return -ENXIO;
|
||||
|
||||
|
|
@ -679,6 +697,203 @@ static int i3c_hci_init(struct i3c_hci *hci)
|
|||
}
|
||||
}
|
||||
|
||||
if (hci->io) {
|
||||
ret = i3c_hci_set_io_mode(hci, hci->io == &mipi_i3c_hci_dma);
|
||||
} else {
|
||||
/* Try activating DMA operations first */
|
||||
if (hci->RHS_regs) {
|
||||
ret = i3c_hci_set_io_mode(hci, true);
|
||||
if (!ret) {
|
||||
hci->io = &mipi_i3c_hci_dma;
|
||||
dev_dbg(&hci->master.dev, "Using DMA\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* If no DMA, try PIO */
|
||||
if (!hci->io && hci->PIO_regs) {
|
||||
ret = i3c_hci_set_io_mode(hci, false);
|
||||
if (!ret) {
|
||||
hci->io = &mipi_i3c_hci_pio;
|
||||
dev_dbg(&hci->master.dev, "Using PIO\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!hci->io) {
|
||||
dev_err(&hci->master.dev, "neither DMA nor PIO can be used\n");
|
||||
ret = ret ?: -EINVAL;
|
||||
}
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Configure OD and PP timings for AMD platforms */
|
||||
if (hci->quirks & HCI_QUIRK_OD_PP_TIMING)
|
||||
amd_set_od_pp_timing(hci);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i3c_hci_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i3c_hci *hci = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = i3c_hci_bus_disable(hci);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hci->io->suspend(hci);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i3c_hci_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i3c_hci *hci = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = i3c_hci_reset_and_init(hci);
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
i3c_hci_set_master_dyn_addr(hci);
|
||||
|
||||
mipi_i3c_hci_dat_v1.restore(hci);
|
||||
|
||||
hci->irq_inactive = false;
|
||||
|
||||
hci->io->resume(hci);
|
||||
|
||||
reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i3c_hci_suspend(struct device *dev)
|
||||
{
|
||||
struct i3c_hci *hci = dev_get_drvdata(dev);
|
||||
|
||||
if (!(hci->quirks & HCI_QUIRK_RPM_ALLOWED))
|
||||
return 0;
|
||||
|
||||
return pm_runtime_force_suspend(dev);
|
||||
}
|
||||
|
||||
static int i3c_hci_resume_common(struct device *dev, bool rstdaa)
|
||||
{
|
||||
struct i3c_hci *hci = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (!(hci->quirks & HCI_QUIRK_RPM_ALLOWED))
|
||||
return 0;
|
||||
|
||||
ret = pm_runtime_force_resume(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = i3c_master_do_daa_ext(&hci->master, rstdaa);
|
||||
if (ret)
|
||||
dev_err(dev, "Dynamic Address Assignment failed on resume, error %d\n", ret);
|
||||
|
||||
/*
|
||||
* I3C devices may have retained their dynamic address anyway. Do not
|
||||
* fail the resume because of DAA error.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i3c_hci_resume(struct device *dev)
|
||||
{
|
||||
return i3c_hci_resume_common(dev, false);
|
||||
}
|
||||
|
||||
static int i3c_hci_restore(struct device *dev)
|
||||
{
|
||||
return i3c_hci_resume_common(dev, true);
|
||||
}
|
||||
|
||||
#define DEFAULT_AUTOSUSPEND_DELAY_MS 1000
|
||||
|
||||
static void i3c_hci_rpm_enable(struct device *dev)
|
||||
{
|
||||
struct i3c_hci *hci = dev_get_drvdata(dev);
|
||||
|
||||
pm_runtime_set_autosuspend_delay(dev, DEFAULT_AUTOSUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
devm_pm_runtime_set_active_enabled(dev);
|
||||
|
||||
hci->master.rpm_allowed = true;
|
||||
}
|
||||
|
||||
static int i3c_hci_init(struct i3c_hci *hci)
|
||||
{
|
||||
bool size_in_dwords;
|
||||
u32 regval, offset;
|
||||
int ret;
|
||||
|
||||
/* Validate HCI hardware version */
|
||||
regval = reg_read(HCI_VERSION);
|
||||
hci->version_major = (regval >> 8) & 0xf;
|
||||
hci->version_minor = (regval >> 4) & 0xf;
|
||||
hci->revision = regval & 0xf;
|
||||
dev_notice(&hci->master.dev, "MIPI I3C HCI v%u.%u r%02u\n",
|
||||
hci->version_major, hci->version_minor, hci->revision);
|
||||
/* known versions */
|
||||
switch (regval & ~0xf) {
|
||||
case 0x100: /* version 1.0 */
|
||||
case 0x110: /* version 1.1 */
|
||||
case 0x200: /* version 2.0 */
|
||||
break;
|
||||
default:
|
||||
dev_err(&hci->master.dev, "unsupported HCI version\n");
|
||||
return -EPROTONOSUPPORT;
|
||||
}
|
||||
|
||||
hci->caps = reg_read(HC_CAPABILITIES);
|
||||
dev_dbg(&hci->master.dev, "caps = %#x", hci->caps);
|
||||
|
||||
size_in_dwords = hci->version_major < 1 ||
|
||||
(hci->version_major == 1 && hci->version_minor < 1);
|
||||
|
||||
regval = reg_read(DAT_SECTION);
|
||||
offset = FIELD_GET(DAT_TABLE_OFFSET, regval);
|
||||
hci->DAT_regs = offset ? hci->base_regs + offset : NULL;
|
||||
hci->DAT_entries = FIELD_GET(DAT_TABLE_SIZE, regval);
|
||||
hci->DAT_entry_size = FIELD_GET(DAT_ENTRY_SIZE, regval) ? 0 : 8;
|
||||
if (size_in_dwords)
|
||||
hci->DAT_entries = 4 * hci->DAT_entries / hci->DAT_entry_size;
|
||||
dev_dbg(&hci->master.dev, "DAT: %u %u-bytes entries at offset %#x\n",
|
||||
hci->DAT_entries, hci->DAT_entry_size, offset);
|
||||
|
||||
regval = reg_read(DCT_SECTION);
|
||||
offset = FIELD_GET(DCT_TABLE_OFFSET, regval);
|
||||
hci->DCT_regs = offset ? hci->base_regs + offset : NULL;
|
||||
hci->DCT_entries = FIELD_GET(DCT_TABLE_SIZE, regval);
|
||||
hci->DCT_entry_size = FIELD_GET(DCT_ENTRY_SIZE, regval) ? 0 : 16;
|
||||
if (size_in_dwords)
|
||||
hci->DCT_entries = 4 * hci->DCT_entries / hci->DCT_entry_size;
|
||||
dev_dbg(&hci->master.dev, "DCT: %u %u-bytes entries at offset %#x\n",
|
||||
hci->DCT_entries, hci->DCT_entry_size, offset);
|
||||
|
||||
regval = reg_read(RING_HEADERS_SECTION);
|
||||
offset = FIELD_GET(RING_HEADERS_OFFSET, regval);
|
||||
hci->RHS_regs = offset ? hci->base_regs + offset : NULL;
|
||||
dev_dbg(&hci->master.dev, "Ring Headers at offset %#x\n", offset);
|
||||
|
||||
regval = reg_read(PIO_SECTION);
|
||||
offset = FIELD_GET(PIO_REGS_OFFSET, regval);
|
||||
hci->PIO_regs = offset ? hci->base_regs + offset : NULL;
|
||||
dev_dbg(&hci->master.dev, "PIO section at offset %#x\n", offset);
|
||||
|
||||
regval = reg_read(EXT_CAPS_SECTION);
|
||||
offset = FIELD_GET(EXT_CAPS_OFFSET, regval);
|
||||
hci->EXTCAPS_regs = offset ? hci->base_regs + offset : NULL;
|
||||
dev_dbg(&hci->master.dev, "Extended Caps at offset %#x\n", offset);
|
||||
|
||||
ret = i3c_hci_parse_ext_caps(hci);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Select our command descriptor model */
|
||||
switch (FIELD_GET(HC_CAP_CMD_SIZE, hci->caps)) {
|
||||
case 0:
|
||||
|
|
@ -692,68 +907,44 @@ static int i3c_hci_init(struct i3c_hci *hci)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
mode_selector = hci->version_major > 1 ||
|
||||
(hci->version_major == 1 && hci->version_minor > 0);
|
||||
|
||||
/* Quirk for HCI_QUIRK_PIO_MODE on AMD platforms */
|
||||
if (hci->quirks & HCI_QUIRK_PIO_MODE)
|
||||
hci->RHS_regs = NULL;
|
||||
|
||||
/* Try activating DMA operations first */
|
||||
if (hci->RHS_regs) {
|
||||
reg_clear(HC_CONTROL, HC_CONTROL_PIO_MODE);
|
||||
if (mode_selector && (reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) {
|
||||
dev_err(&hci->master.dev, "PIO mode is stuck\n");
|
||||
ret = -EIO;
|
||||
} else {
|
||||
hci->io = &mipi_i3c_hci_dma;
|
||||
dev_info(&hci->master.dev, "Using DMA\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* If no DMA, try PIO */
|
||||
if (!hci->io && hci->PIO_regs) {
|
||||
reg_set(HC_CONTROL, HC_CONTROL_PIO_MODE);
|
||||
if (mode_selector && !(reg_read(HC_CONTROL) & HC_CONTROL_PIO_MODE)) {
|
||||
dev_err(&hci->master.dev, "DMA mode is stuck\n");
|
||||
ret = -EIO;
|
||||
} else {
|
||||
hci->io = &mipi_i3c_hci_pio;
|
||||
dev_info(&hci->master.dev, "Using PIO\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!hci->io) {
|
||||
dev_err(&hci->master.dev, "neither DMA nor PIO can be used\n");
|
||||
if (!ret)
|
||||
ret = -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Configure OD and PP timings for AMD platforms */
|
||||
if (hci->quirks & HCI_QUIRK_OD_PP_TIMING)
|
||||
amd_set_od_pp_timing(hci);
|
||||
|
||||
return 0;
|
||||
return i3c_hci_reset_and_init(hci);
|
||||
}
|
||||
|
||||
static int i3c_hci_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct mipi_i3c_hci_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct i3c_hci *hci;
|
||||
int irq, ret;
|
||||
|
||||
hci = devm_kzalloc(&pdev->dev, sizeof(*hci), GFP_KERNEL);
|
||||
if (!hci)
|
||||
return -ENOMEM;
|
||||
hci->base_regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(hci->base_regs))
|
||||
return PTR_ERR(hci->base_regs);
|
||||
|
||||
/*
|
||||
* Multi-bus instances share the same MMIO address range, but not
|
||||
* necessarily in separate contiguous sub-ranges. To avoid overlapping
|
||||
* mappings, provide base_regs from the parent mapping.
|
||||
*/
|
||||
if (pdata)
|
||||
hci->base_regs = pdata->base_regs;
|
||||
|
||||
if (!hci->base_regs) {
|
||||
hci->base_regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(hci->base_regs))
|
||||
return PTR_ERR(hci->base_regs);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, hci);
|
||||
/* temporary for dev_printk's, to be replaced in i3c_master_register */
|
||||
hci->master.dev.init_name = dev_name(&pdev->dev);
|
||||
|
||||
hci->quirks = (unsigned long)device_get_match_data(&pdev->dev);
|
||||
if (!hci->quirks && platform_get_device_id(pdev))
|
||||
hci->quirks = platform_get_device_id(pdev)->driver_data;
|
||||
|
||||
ret = i3c_hci_init(hci);
|
||||
if (ret)
|
||||
|
|
@ -761,16 +952,14 @@ static int i3c_hci_probe(struct platform_device *pdev)
|
|||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
ret = devm_request_irq(&pdev->dev, irq, i3c_hci_irq_handler,
|
||||
0, NULL, hci);
|
||||
IRQF_SHARED, NULL, hci);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = i3c_master_register(&hci->master, &pdev->dev,
|
||||
&i3c_hci_ops, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (hci->quirks & HCI_QUIRK_RPM_ALLOWED)
|
||||
i3c_hci_rpm_enable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false);
|
||||
}
|
||||
|
||||
static void i3c_hci_remove(struct platform_device *pdev)
|
||||
|
|
@ -792,13 +981,31 @@ static const struct acpi_device_id i3c_hci_acpi_match[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(acpi, i3c_hci_acpi_match);
|
||||
|
||||
static const struct platform_device_id i3c_hci_driver_ids[] = {
|
||||
{ .name = "intel-lpss-i3c", HCI_QUIRK_RPM_ALLOWED },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids);
|
||||
|
||||
static const struct dev_pm_ops i3c_hci_pm_ops = {
|
||||
.suspend = pm_sleep_ptr(i3c_hci_suspend),
|
||||
.resume = pm_sleep_ptr(i3c_hci_resume),
|
||||
.freeze = pm_sleep_ptr(i3c_hci_suspend),
|
||||
.thaw = pm_sleep_ptr(i3c_hci_resume),
|
||||
.poweroff = pm_sleep_ptr(i3c_hci_suspend),
|
||||
.restore = pm_sleep_ptr(i3c_hci_restore),
|
||||
RUNTIME_PM_OPS(i3c_hci_runtime_suspend, i3c_hci_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct platform_driver i3c_hci_driver = {
|
||||
.probe = i3c_hci_probe,
|
||||
.remove = i3c_hci_remove,
|
||||
.id_table = i3c_hci_driver_ids,
|
||||
.driver = {
|
||||
.name = "mipi-i3c-hci",
|
||||
.of_match_table = of_match_ptr(i3c_hci_of_match),
|
||||
.acpi_match_table = i3c_hci_acpi_match,
|
||||
.pm = pm_ptr(&i3c_hci_pm_ops),
|
||||
},
|
||||
};
|
||||
module_platform_driver(i3c_hci_driver);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
struct hci_dat_ops {
|
||||
int (*init)(struct i3c_hci *hci);
|
||||
void (*cleanup)(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);
|
||||
|
|
@ -25,6 +24,7 @@ struct hci_dat_ops {
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#include "hci.h"
|
||||
#include "dat.h"
|
||||
|
||||
|
||||
/*
|
||||
* Device Address Table Structure
|
||||
*/
|
||||
|
|
@ -35,13 +34,26 @@
|
|||
/* DAT_0_IBI_PAYLOAD W0_BIT_(12) */
|
||||
#define DAT_0_STATIC_ADDRESS W0_MASK(6, 0)
|
||||
|
||||
#define dat_w0_read(i) readl(hci->DAT_regs + (i) * 8)
|
||||
#define dat_w1_read(i) readl(hci->DAT_regs + (i) * 8 + 4)
|
||||
#define dat_w0_write(i, v) writel(v, hci->DAT_regs + (i) * 8)
|
||||
#define dat_w1_write(i, v) writel(v, hci->DAT_regs + (i) * 8 + 4)
|
||||
#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) {
|
||||
|
|
@ -55,9 +67,15 @@ static int hci_dat_v1_init(struct i3c_hci *hci)
|
|||
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 = bitmap_zalloc(hci->DAT_entries, GFP_KERNEL);
|
||||
hci->DAT_data = devm_bitmap_zalloc(dev, hci->DAT_entries, GFP_KERNEL);
|
||||
if (!hci->DAT_data)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -71,12 +89,6 @@ static int hci_dat_v1_init(struct i3c_hci *hci)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void hci_dat_v1_cleanup(struct i3c_hci *hci)
|
||||
{
|
||||
bitmap_free(hci->DAT_data);
|
||||
hci->DAT_data = NULL;
|
||||
}
|
||||
|
||||
static int hci_dat_v1_alloc_entry(struct i3c_hci *hci)
|
||||
{
|
||||
unsigned int dat_idx;
|
||||
|
|
@ -169,9 +181,16 @@ static int hci_dat_v1_get_index(struct i3c_hci *hci, u8 dev_addr)
|
|||
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,
|
||||
.cleanup = hci_dat_v1_cleanup,
|
||||
.alloc_entry = hci_dat_v1_alloc_entry,
|
||||
.free_entry = hci_dat_v1_free_entry,
|
||||
.set_dynamic_addr = hci_dat_v1_set_dynamic_addr,
|
||||
|
|
@ -179,4 +198,5 @@ const struct hci_dat_ops mipi_i3c_hci_dat_v1 = {
|
|||
.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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
#include "cmd.h"
|
||||
#include "ibi.h"
|
||||
|
||||
|
||||
/*
|
||||
* Software Parameter Values (somewhat arb itrary for now).
|
||||
* Some of them could be determined at run time eventually.
|
||||
|
|
@ -124,7 +123,6 @@
|
|||
#define DATA_BUF_IOC BIT(30) /* Interrupt on Completion */
|
||||
#define DATA_BUF_BLOCK_SIZE GENMASK(15, 0)
|
||||
|
||||
|
||||
struct hci_rh_data {
|
||||
void __iomem *regs;
|
||||
void *xfer, *resp, *ibi_status, *ibi_data;
|
||||
|
|
@ -162,8 +160,31 @@ static void hci_dma_cleanup(struct i3c_hci *hci)
|
|||
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, 0);
|
||||
rh_reg_write(RING_CONTROL, 0);
|
||||
}
|
||||
|
||||
i3c_hci_sync_irq_inactive(hci);
|
||||
|
||||
for (i = 0; i < rings->total; i++) {
|
||||
rh = &rings->headers[i];
|
||||
|
||||
rh_reg_write(CR_SETUP, 0);
|
||||
rh_reg_write(IBI_SETUP, 0);
|
||||
}
|
||||
|
||||
rhs_reg_write(CONTROL, 0);
|
||||
}
|
||||
|
||||
static void hci_dma_free(void *data)
|
||||
{
|
||||
struct i3c_hci *hci = data;
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
struct hci_rh_data *rh;
|
||||
|
||||
if (!rings)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < rings->total; i++) {
|
||||
rh = &rings->headers[i];
|
||||
|
||||
if (rh->xfer)
|
||||
dma_free_coherent(rings->sysdev,
|
||||
|
|
@ -185,12 +206,98 @@ static void hci_dma_cleanup(struct i3c_hci *hci)
|
|||
kfree(rh->ibi_data);
|
||||
}
|
||||
|
||||
rhs_reg_write(CONTROL, 0);
|
||||
|
||||
kfree(rings);
|
||||
hci->io_data = NULL;
|
||||
}
|
||||
|
||||
static void hci_dma_init_rh(struct i3c_hci *hci, struct hci_rh_data *rh, int i)
|
||||
{
|
||||
u32 regval;
|
||||
|
||||
rh_reg_write(CMD_RING_BASE_LO, lower_32_bits(rh->xfer_dma));
|
||||
rh_reg_write(CMD_RING_BASE_HI, upper_32_bits(rh->xfer_dma));
|
||||
rh_reg_write(RESP_RING_BASE_LO, lower_32_bits(rh->resp_dma));
|
||||
rh_reg_write(RESP_RING_BASE_HI, upper_32_bits(rh->resp_dma));
|
||||
|
||||
regval = FIELD_PREP(CR_RING_SIZE, rh->xfer_entries);
|
||||
rh_reg_write(CR_SETUP, regval);
|
||||
|
||||
rh_reg_write(INTR_STATUS_ENABLE, 0xffffffff);
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, INTR_IBI_READY |
|
||||
INTR_TRANSFER_COMPLETION |
|
||||
INTR_RING_OP |
|
||||
INTR_TRANSFER_ERR |
|
||||
INTR_IBI_RING_FULL |
|
||||
INTR_TRANSFER_ABORT);
|
||||
|
||||
if (i >= IBI_RINGS)
|
||||
goto ring_ready;
|
||||
|
||||
rh_reg_write(IBI_STATUS_RING_BASE_LO, lower_32_bits(rh->ibi_status_dma));
|
||||
rh_reg_write(IBI_STATUS_RING_BASE_HI, upper_32_bits(rh->ibi_status_dma));
|
||||
rh_reg_write(IBI_DATA_RING_BASE_LO, lower_32_bits(rh->ibi_data_dma));
|
||||
rh_reg_write(IBI_DATA_RING_BASE_HI, upper_32_bits(rh->ibi_data_dma));
|
||||
|
||||
regval = FIELD_PREP(IBI_STATUS_RING_SIZE, rh->ibi_status_entries) |
|
||||
FIELD_PREP(IBI_DATA_CHUNK_SIZE, ilog2(rh->ibi_chunk_sz) - 2) |
|
||||
FIELD_PREP(IBI_DATA_CHUNK_COUNT, rh->ibi_chunks_total);
|
||||
rh_reg_write(IBI_SETUP, regval);
|
||||
|
||||
regval = rh_reg_read(INTR_SIGNAL_ENABLE);
|
||||
regval |= INTR_IBI_READY;
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, regval);
|
||||
|
||||
ring_ready:
|
||||
/*
|
||||
* The MIPI I3C HCI specification does not document reset values for
|
||||
* RING_OPERATION1 fields and some controllers (e.g. Intel controllers)
|
||||
* do not reset the values, so ensure the ring pointers are set to zero
|
||||
* here.
|
||||
*/
|
||||
rh_reg_write(RING_OPERATION1, 0);
|
||||
|
||||
rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE);
|
||||
rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE | RING_CTRL_RUN_STOP);
|
||||
|
||||
rh->done_ptr = 0;
|
||||
rh->ibi_chunk_ptr = 0;
|
||||
}
|
||||
|
||||
static void hci_dma_init_rings(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
u32 regval;
|
||||
|
||||
regval = FIELD_PREP(MAX_HEADER_COUNT, rings->total);
|
||||
rhs_reg_write(CONTROL, regval);
|
||||
|
||||
for (int i = 0; i < rings->total; i++)
|
||||
hci_dma_init_rh(hci, &rings->headers[i], i);
|
||||
}
|
||||
|
||||
static void hci_dma_suspend(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
int n = rings ? rings->total : 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
struct hci_rh_data *rh = &rings->headers[i];
|
||||
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, 0);
|
||||
rh_reg_write(RING_CONTROL, 0);
|
||||
}
|
||||
|
||||
i3c_hci_sync_irq_inactive(hci);
|
||||
}
|
||||
|
||||
static void hci_dma_resume(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings = hci->io_data;
|
||||
|
||||
if (rings)
|
||||
hci_dma_init_rings(hci);
|
||||
}
|
||||
|
||||
static int hci_dma_init(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_rings_data *rings;
|
||||
|
|
@ -214,7 +321,7 @@ static int hci_dma_init(struct i3c_hci *hci)
|
|||
|
||||
regval = rhs_reg_read(CONTROL);
|
||||
nr_rings = FIELD_GET(MAX_HEADER_COUNT_CAP, regval);
|
||||
dev_info(&hci->master.dev, "%d DMA rings available\n", nr_rings);
|
||||
dev_dbg(&hci->master.dev, "%d DMA rings available\n", nr_rings);
|
||||
if (unlikely(nr_rings > 8)) {
|
||||
dev_err(&hci->master.dev, "number of rings should be <= 8\n");
|
||||
nr_rings = 8;
|
||||
|
|
@ -228,13 +335,10 @@ static int hci_dma_init(struct i3c_hci *hci)
|
|||
rings->total = nr_rings;
|
||||
rings->sysdev = sysdev;
|
||||
|
||||
regval = FIELD_PREP(MAX_HEADER_COUNT, rings->total);
|
||||
rhs_reg_write(CONTROL, regval);
|
||||
|
||||
for (i = 0; i < rings->total; i++) {
|
||||
u32 offset = rhs_reg_read(RHn_OFFSET(i));
|
||||
|
||||
dev_info(&hci->master.dev, "Ring %d at offset %#x\n", i, offset);
|
||||
dev_dbg(&hci->master.dev, "Ring %d at offset %#x\n", i, offset);
|
||||
ret = -EINVAL;
|
||||
if (!offset)
|
||||
goto err_out;
|
||||
|
|
@ -265,26 +369,10 @@ static int hci_dma_init(struct i3c_hci *hci)
|
|||
if (!rh->xfer || !rh->resp || !rh->src_xfers)
|
||||
goto err_out;
|
||||
|
||||
rh_reg_write(CMD_RING_BASE_LO, lower_32_bits(rh->xfer_dma));
|
||||
rh_reg_write(CMD_RING_BASE_HI, upper_32_bits(rh->xfer_dma));
|
||||
rh_reg_write(RESP_RING_BASE_LO, lower_32_bits(rh->resp_dma));
|
||||
rh_reg_write(RESP_RING_BASE_HI, upper_32_bits(rh->resp_dma));
|
||||
|
||||
regval = FIELD_PREP(CR_RING_SIZE, rh->xfer_entries);
|
||||
rh_reg_write(CR_SETUP, regval);
|
||||
|
||||
rh_reg_write(INTR_STATUS_ENABLE, 0xffffffff);
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, INTR_IBI_READY |
|
||||
INTR_TRANSFER_COMPLETION |
|
||||
INTR_RING_OP |
|
||||
INTR_TRANSFER_ERR |
|
||||
INTR_IBI_RING_FULL |
|
||||
INTR_TRANSFER_ABORT);
|
||||
|
||||
/* IBIs */
|
||||
|
||||
if (i >= IBI_RINGS)
|
||||
goto ring_ready;
|
||||
continue;
|
||||
|
||||
regval = rh_reg_read(IBI_SETUP);
|
||||
rh->ibi_status_sz = FIELD_GET(IBI_STATUS_STRUCT_SIZE, regval);
|
||||
|
|
@ -323,33 +411,18 @@ static int hci_dma_init(struct i3c_hci *hci)
|
|||
ret = -ENOMEM;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
rh_reg_write(IBI_STATUS_RING_BASE_LO, lower_32_bits(rh->ibi_status_dma));
|
||||
rh_reg_write(IBI_STATUS_RING_BASE_HI, upper_32_bits(rh->ibi_status_dma));
|
||||
rh_reg_write(IBI_DATA_RING_BASE_LO, lower_32_bits(rh->ibi_data_dma));
|
||||
rh_reg_write(IBI_DATA_RING_BASE_HI, upper_32_bits(rh->ibi_data_dma));
|
||||
|
||||
regval = FIELD_PREP(IBI_STATUS_RING_SIZE,
|
||||
rh->ibi_status_entries) |
|
||||
FIELD_PREP(IBI_DATA_CHUNK_SIZE,
|
||||
ilog2(rh->ibi_chunk_sz) - 2) |
|
||||
FIELD_PREP(IBI_DATA_CHUNK_COUNT,
|
||||
rh->ibi_chunks_total);
|
||||
rh_reg_write(IBI_SETUP, regval);
|
||||
|
||||
regval = rh_reg_read(INTR_SIGNAL_ENABLE);
|
||||
regval |= INTR_IBI_READY;
|
||||
rh_reg_write(INTR_SIGNAL_ENABLE, regval);
|
||||
|
||||
ring_ready:
|
||||
rh_reg_write(RING_CONTROL, RING_CTRL_ENABLE |
|
||||
RING_CTRL_RUN_STOP);
|
||||
}
|
||||
|
||||
ret = devm_add_action(hci->master.dev.parent, hci_dma_free, hci);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
hci_dma_init_rings(hci);
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
hci_dma_cleanup(hci);
|
||||
hci_dma_free(hci);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -815,4 +888,6 @@ const struct hci_io_ops mipi_i3c_hci_dma = {
|
|||
.request_ibi = hci_dma_request_ibi,
|
||||
.free_ibi = hci_dma_free_ibi,
|
||||
.recycle_ibi_slot = hci_dma_recycle_ibi_slot,
|
||||
.suspend = hci_dma_suspend,
|
||||
.resume = hci_dma_resume,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
#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)
|
||||
|
|
@ -27,9 +26,9 @@ static int hci_extcap_hardware_id(struct i3c_hci *hci, void __iomem *base)
|
|||
hci->vendor_version_id = readl(base + 0x08);
|
||||
hci->vendor_product_id = readl(base + 0x0c);
|
||||
|
||||
dev_info(&hci->master.dev, "vendor MIPI ID: %#x\n", hci->vendor_mipi_id);
|
||||
dev_info(&hci->master.dev, "vendor version ID: %#x\n", hci->vendor_version_id);
|
||||
dev_info(&hci->master.dev, "vendor product ID: %#x\n", hci->vendor_product_id);
|
||||
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) {
|
||||
|
|
@ -49,7 +48,7 @@ static int hci_extcap_master_config(struct i3c_hci *hci, void __iomem *base)
|
|||
static const char * const functionality[] = {
|
||||
"(unknown)", "master only", "target only",
|
||||
"primary/secondary master" };
|
||||
dev_info(&hci->master.dev, "operation mode: %s\n", functionality[operation_mode]);
|
||||
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");
|
||||
|
|
@ -61,7 +60,7 @@ 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_info(&hci->master.dev, "%d bus instances\n", count);
|
||||
dev_dbg(&hci->master.dev, "%d bus instances\n", count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -71,8 +70,7 @@ static int hci_extcap_xfer_modes(struct i3c_hci *hci, void __iomem *base)
|
|||
u32 entries = FIELD_GET(CAP_HEADER_LENGTH, header) - 1;
|
||||
unsigned int index;
|
||||
|
||||
dev_info(&hci->master.dev, "transfer mode table has %d entries\n",
|
||||
entries);
|
||||
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);
|
||||
|
|
@ -95,7 +93,7 @@ static int hci_extcap_xfer_rates(struct i3c_hci *hci, void __iomem *base)
|
|||
|
||||
base += 4; /* skip header */
|
||||
|
||||
dev_info(&hci->master.dev, "available data rates:\n");
|
||||
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",
|
||||
|
|
@ -103,12 +101,12 @@ static int hci_extcap_xfer_rates(struct i3c_hci *hci, void __iomem *base)
|
|||
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_info(&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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -122,8 +120,8 @@ static int hci_extcap_auto_command(struct i3c_hci *hci, void __iomem *base)
|
|||
u32 autocmd_ext_config = readl(base + 0x08);
|
||||
unsigned int count = FIELD_GET(GENMASK(3, 0), autocmd_ext_config);
|
||||
|
||||
dev_info(&hci->master.dev, "%d/%d active auto-command entries\n",
|
||||
count, max_count);
|
||||
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;
|
||||
|
|
@ -131,46 +129,46 @@ static int hci_extcap_auto_command(struct i3c_hci *hci, void __iomem *base)
|
|||
|
||||
static int hci_extcap_debug(struct i3c_hci *hci, void __iomem *base)
|
||||
{
|
||||
dev_info(&hci->master.dev, "debug registers present\n");
|
||||
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_info(&hci->master.dev, "scheduled commands available\n");
|
||||
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_info(&hci->master.dev, "Non-Current Master support available\n");
|
||||
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_info(&hci->master.dev, "CCC Response Configuration available\n");
|
||||
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_info(&hci->master.dev, "Global DAT available\n");
|
||||
dev_dbg(&hci->master.dev, "Global DAT available\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hci_extcap_multilane(struct i3c_hci *hci, void __iomem *base)
|
||||
{
|
||||
dev_info(&hci->master.dev, "Master Multi-Lane support available\n");
|
||||
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_info(&hci->master.dev, "NCM Multi-Lane support available\n");
|
||||
dev_dbg(&hci->master.dev, "NCM Multi-Lane support available\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +201,7 @@ static const struct hci_ext_caps ext_capabilities[] = {
|
|||
static int hci_extcap_vendor_NXP(struct i3c_hci *hci, void __iomem *base)
|
||||
{
|
||||
hci->vendor_data = (__force void *)base;
|
||||
dev_info(&hci->master.dev, "Build Date Info = %#x\n", readl(base + 1*4));
|
||||
dev_dbg(&hci->master.dev, "Build Date Info = %#x\n", readl(base + 1 * 4));
|
||||
/* reset the FPGA */
|
||||
writel(0xdeadbeef, base + 1*4);
|
||||
return 0;
|
||||
|
|
@ -241,9 +239,8 @@ static int hci_extcap_vendor_specific(struct i3c_hci *hci, void __iomem *base,
|
|||
}
|
||||
|
||||
if (!vendor_cap_entry) {
|
||||
dev_notice(&hci->master.dev,
|
||||
"unknown ext_cap 0x%02x for vendor 0x%02x\n",
|
||||
cap_id, hci->vendor_mipi_id);
|
||||
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) {
|
||||
|
|
@ -272,7 +269,7 @@ int i3c_hci_parse_ext_caps(struct i3c_hci *hci)
|
|||
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_length)
|
||||
if (!cap_id || !cap_length)
|
||||
break;
|
||||
if (curr_cap + cap_length * 4 >= end) {
|
||||
dev_err(&hci->master.dev,
|
||||
|
|
@ -296,8 +293,7 @@ int i3c_hci_parse_ext_caps(struct i3c_hci *hci)
|
|||
}
|
||||
}
|
||||
if (!cap_entry) {
|
||||
dev_notice(&hci->master.dev,
|
||||
"unknown ext_cap 0x%02x\n", cap_id);
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
/* MIPI vendor IDs */
|
||||
#define MIPI_VENDOR_NXP 0x11b
|
||||
|
||||
|
||||
int i3c_hci_parse_ext_caps(struct i3c_hci *hci);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@
|
|||
|
||||
struct hci_cmd_ops;
|
||||
|
||||
struct dat_words {
|
||||
u32 w0;
|
||||
u32 w1;
|
||||
};
|
||||
|
||||
/* Our main structure */
|
||||
struct i3c_hci {
|
||||
struct i3c_master_controller master;
|
||||
|
|
@ -46,23 +51,25 @@ struct i3c_hci {
|
|||
void *io_data;
|
||||
const struct hci_cmd_ops *cmd;
|
||||
atomic_t next_cmd_tid;
|
||||
bool irq_inactive;
|
||||
u32 caps;
|
||||
unsigned int quirks;
|
||||
unsigned int DAT_entries;
|
||||
unsigned int DAT_entry_size;
|
||||
void *DAT_data;
|
||||
struct dat_words *DAT;
|
||||
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
|
||||
|
|
@ -108,7 +115,6 @@ static inline void hci_free_xfer(struct hci_xfer *xfer, unsigned int n)
|
|||
kfree(xfer);
|
||||
}
|
||||
|
||||
|
||||
/* This abstracts PIO vs DMA operations */
|
||||
struct hci_io_ops {
|
||||
bool (*irq_handler)(struct i3c_hci *hci);
|
||||
|
|
@ -121,25 +127,25 @@ struct hci_io_ops {
|
|||
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 */
|
||||
|
||||
/* global functions */
|
||||
void mipi_i3c_hci_resume(struct i3c_hci *hci);
|
||||
|
|
@ -147,5 +153,6 @@ void mipi_i3c_hci_pio_reset(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);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -12,14 +12,23 @@
|
|||
#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>
|
||||
|
||||
/*
|
||||
* There can up to 15 instances, but implementations have at most 2 at this
|
||||
* time.
|
||||
*/
|
||||
#define INST_MAX 2
|
||||
|
||||
struct mipi_i3c_hci_pci {
|
||||
struct pci_dev *pci;
|
||||
struct platform_device *pdev;
|
||||
void __iomem *base;
|
||||
const struct mipi_i3c_hci_pci_info *info;
|
||||
void *private;
|
||||
};
|
||||
|
|
@ -27,12 +36,13 @@ struct mipi_i3c_hci_pci {
|
|||
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;
|
||||
};
|
||||
|
||||
static DEFINE_IDA(mipi_i3c_hci_pci_ida);
|
||||
|
||||
#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)
|
||||
|
|
@ -143,19 +153,12 @@ static void intel_reset(void __iomem *priv)
|
|||
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);
|
||||
void __iomem *priv = hci->base + INTEL_PRIV_OFFSET;
|
||||
|
||||
if (!host || !priv)
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
|
||||
dma_set_mask_and_coherent(&hci->pci->dev, DMA_BIT_MASK(64));
|
||||
|
|
@ -179,17 +182,89 @@ static void intel_i3c_exit(struct mipi_i3c_hci_pci *hci)
|
|||
intel_ltr_hide(&hci->pci->dev);
|
||||
}
|
||||
|
||||
static const struct mipi_i3c_hci_pci_info intel_info = {
|
||||
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,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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) kcalloc((h)->info->instance_count, sizeof(*(x)), GFP_KERNEL)
|
||||
|
||||
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;
|
||||
struct resource res[2];
|
||||
int dev_id, ret;
|
||||
int ret;
|
||||
|
||||
hci = devm_kzalloc(&pci->dev, sizeof(*hci), GFP_KERNEL);
|
||||
if (!hci)
|
||||
|
|
@ -203,81 +278,65 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci,
|
|||
|
||||
pci_set_master(pci);
|
||||
|
||||
memset(&res, 0, sizeof(res));
|
||||
hci->base = pcim_iomap_region(pci, 0, pci_name(pci));
|
||||
if (IS_ERR(hci->base))
|
||||
return PTR_ERR(hci->base);
|
||||
|
||||
res[0].flags = IORESOURCE_MEM;
|
||||
res[0].start = pci_resource_start(pci, 0);
|
||||
res[0].end = pci_resource_end(pci, 0);
|
||||
|
||||
res[1].flags = IORESOURCE_IRQ;
|
||||
res[1].start = pci->irq;
|
||||
res[1].end = pci->irq;
|
||||
|
||||
dev_id = ida_alloc(&mipi_i3c_hci_pci_ida, GFP_KERNEL);
|
||||
if (dev_id < 0)
|
||||
return dev_id;
|
||||
|
||||
hci->pdev = platform_device_alloc("mipi-i3c-hci", dev_id);
|
||||
if (!hci->pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
hci->pdev->dev.parent = &pci->dev;
|
||||
device_set_node(&hci->pdev->dev, dev_fwnode(&pci->dev));
|
||||
|
||||
ret = platform_device_add_resources(hci->pdev, res, ARRAY_SIZE(res));
|
||||
if (ret)
|
||||
goto err;
|
||||
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;
|
||||
if (hci->info && hci->info->init) {
|
||||
ret = hci->info->init(hci);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = platform_device_add(hci->pdev);
|
||||
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 && hci->info->exit)
|
||||
if (hci->info->exit)
|
||||
hci->info->exit(hci);
|
||||
err:
|
||||
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 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)
|
||||
if (hci->info->exit)
|
||||
hci->info->exit(hci);
|
||||
|
||||
platform_device_unregister(pdev);
|
||||
ida_free(&mipi_i3c_hci_pci_ida, dev_id);
|
||||
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 = {
|
||||
};
|
||||
|
||||
static const struct pci_device_id mipi_i3c_hci_pci_devices[] = {
|
||||
/* Wildcat Lake-U */
|
||||
{ PCI_VDEVICE(INTEL, 0x4d7c), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0x4d6f), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0x4d7c), (kernel_ulong_t)&intel_mi_1_info},
|
||||
{ PCI_VDEVICE(INTEL, 0x4d6f), (kernel_ulong_t)&intel_si_2_info},
|
||||
/* Panther Lake-H */
|
||||
{ PCI_VDEVICE(INTEL, 0xe37c), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0xe36f), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0xe37c), (kernel_ulong_t)&intel_mi_1_info},
|
||||
{ PCI_VDEVICE(INTEL, 0xe36f), (kernel_ulong_t)&intel_si_2_info},
|
||||
/* Panther Lake-P */
|
||||
{ PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0xe47c), (kernel_ulong_t)&intel_mi_1_info},
|
||||
{ PCI_VDEVICE(INTEL, 0xe46f), (kernel_ulong_t)&intel_si_2_info},
|
||||
/* Nova Lake-S */
|
||||
{ PCI_VDEVICE(INTEL, 0x6e2c), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0x6e2d), (kernel_ulong_t)&intel_info},
|
||||
{ PCI_VDEVICE(INTEL, 0x6e2c), (kernel_ulong_t)&intel_mi_1_info},
|
||||
{ PCI_VDEVICE(INTEL, 0x6e2d), (kernel_ulong_t)&intel_mi_2_info},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices);
|
||||
|
|
@ -287,6 +346,9 @@ static struct pci_driver mipi_i3c_hci_pci_driver = {
|
|||
.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);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#include "cmd.h"
|
||||
#include "ibi.h"
|
||||
|
||||
|
||||
/*
|
||||
* PIO Access Area
|
||||
*/
|
||||
|
|
@ -136,27 +135,14 @@ struct hci_pio_data {
|
|||
u32 enabled_irqs;
|
||||
};
|
||||
|
||||
static int hci_pio_init(struct i3c_hci *hci)
|
||||
static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr)
|
||||
{
|
||||
struct hci_pio_data *pio;
|
||||
u32 val, size_val, rx_thresh, tx_thresh, ibi_val;
|
||||
|
||||
pio = kzalloc(sizeof(*pio), GFP_KERNEL);
|
||||
if (!pio)
|
||||
return -ENOMEM;
|
||||
|
||||
hci->io_data = pio;
|
||||
spin_lock_init(&pio->lock);
|
||||
struct hci_pio_data *pio = hci->io_data;
|
||||
|
||||
size_val = pio_reg_read(QUEUE_SIZE);
|
||||
dev_info(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n",
|
||||
FIELD_GET(CR_QUEUE_SIZE, size_val));
|
||||
dev_info(&hci->master.dev, "IBI FIFO = %ld bytes\n",
|
||||
4 * FIELD_GET(IBI_STATUS_SIZE, size_val));
|
||||
dev_info(&hci->master.dev, "RX data FIFO = %d bytes\n",
|
||||
4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val)));
|
||||
dev_info(&hci->master.dev, "TX data FIFO = %d bytes\n",
|
||||
4 * (2 << FIELD_GET(TX_DATA_BUFFER_SIZE, size_val)));
|
||||
if (size_val_ptr)
|
||||
*size_val_ptr = size_val;
|
||||
|
||||
/*
|
||||
* Let's initialize data thresholds to half of the actual FIFO size.
|
||||
|
|
@ -202,6 +188,42 @@ static int hci_pio_init(struct i3c_hci *hci)
|
|||
|
||||
/* Always accept error interrupts (will be activated on first xfer) */
|
||||
pio->enabled_irqs = STAT_ALL_ERRORS;
|
||||
}
|
||||
|
||||
static void hci_pio_suspend(struct i3c_hci *hci)
|
||||
{
|
||||
pio_reg_write(INTR_SIGNAL_ENABLE, 0);
|
||||
|
||||
i3c_hci_sync_irq_inactive(hci);
|
||||
}
|
||||
|
||||
static void hci_pio_resume(struct i3c_hci *hci)
|
||||
{
|
||||
__hci_pio_init(hci, NULL);
|
||||
}
|
||||
|
||||
static int hci_pio_init(struct i3c_hci *hci)
|
||||
{
|
||||
struct hci_pio_data *pio;
|
||||
u32 size_val;
|
||||
|
||||
pio = devm_kzalloc(hci->master.dev.parent, sizeof(*pio), GFP_KERNEL);
|
||||
if (!pio)
|
||||
return -ENOMEM;
|
||||
|
||||
hci->io_data = pio;
|
||||
spin_lock_init(&pio->lock);
|
||||
|
||||
__hci_pio_init(hci, &size_val);
|
||||
|
||||
dev_dbg(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n",
|
||||
FIELD_GET(CR_QUEUE_SIZE, size_val));
|
||||
dev_dbg(&hci->master.dev, "IBI FIFO = %ld bytes\n",
|
||||
4 * FIELD_GET(IBI_STATUS_SIZE, size_val));
|
||||
dev_dbg(&hci->master.dev, "RX data FIFO = %d bytes\n",
|
||||
4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val)));
|
||||
dev_dbg(&hci->master.dev, "TX data FIFO = %d bytes\n",
|
||||
4 * (2 << FIELD_GET(TX_DATA_BUFFER_SIZE, size_val)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -212,6 +234,8 @@ static void hci_pio_cleanup(struct i3c_hci *hci)
|
|||
|
||||
pio_reg_write(INTR_SIGNAL_ENABLE, 0x0);
|
||||
|
||||
i3c_hci_sync_irq_inactive(hci);
|
||||
|
||||
if (pio) {
|
||||
dev_dbg(&hci->master.dev, "status = %#x/%#x",
|
||||
pio_reg_read(INTR_STATUS), pio_reg_read(INTR_SIGNAL_ENABLE));
|
||||
|
|
@ -219,8 +243,6 @@ static void hci_pio_cleanup(struct i3c_hci *hci)
|
|||
BUG_ON(pio->curr_rx);
|
||||
BUG_ON(pio->curr_tx);
|
||||
BUG_ON(pio->curr_resp);
|
||||
kfree(pio);
|
||||
hci->io_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1049,4 +1071,6 @@ const struct hci_io_ops mipi_i3c_hci_pio = {
|
|||
.request_ibi = hci_pio_request_ibi,
|
||||
.free_ibi = hci_pio_free_ibi,
|
||||
.recycle_ibi_slot = hci_pio_recycle_ibi_slot,
|
||||
.suspend = hci_pio_suspend,
|
||||
.resume = hci_pio_resume,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -198,6 +198,8 @@
|
|||
#define RENESAS_I3C_MAX_DEVS 8
|
||||
#define I2C_INIT_MSG -1
|
||||
|
||||
#define RENESAS_I3C_TCLK_IDX 1
|
||||
|
||||
enum i3c_internal_state {
|
||||
I3C_INTERNAL_STATE_DISABLED,
|
||||
I3C_INTERNAL_STATE_CONTROLLER_IDLE,
|
||||
|
|
@ -254,12 +256,19 @@ struct renesas_i3c {
|
|||
enum i3c_internal_state internal_state;
|
||||
u16 maxdevs;
|
||||
u32 free_pos;
|
||||
u32 dyn_addr;
|
||||
u32 i2c_STDBR;
|
||||
u32 i3c_STDBR;
|
||||
unsigned long rate;
|
||||
u8 addrs[RENESAS_I3C_MAX_DEVS];
|
||||
struct renesas_i3c_xferqueue xferqueue;
|
||||
void __iomem *regs;
|
||||
struct clk *tclk;
|
||||
u32 *DATBASn;
|
||||
struct clk_bulk_data *clks;
|
||||
struct reset_control *presetn;
|
||||
struct reset_control *tresetn;
|
||||
u8 num_clks;
|
||||
u8 refclk_div;
|
||||
};
|
||||
|
||||
struct renesas_i3c_i2c_dev_data {
|
||||
|
|
@ -272,10 +281,6 @@ struct renesas_i3c_irq_desc {
|
|||
const char *desc;
|
||||
};
|
||||
|
||||
struct renesas_i3c_config {
|
||||
unsigned int has_pclkrw:1;
|
||||
};
|
||||
|
||||
static inline void renesas_i3c_reg_update(void __iomem *reg, u32 mask, u32 val)
|
||||
{
|
||||
u32 data = readl(reg);
|
||||
|
|
@ -477,92 +482,16 @@ static int renesas_i3c_reset(struct renesas_i3c *i3c)
|
|||
0, 1000, false, i3c->regs, RSTCTL);
|
||||
}
|
||||
|
||||
static int renesas_i3c_bus_init(struct i3c_master_controller *m)
|
||||
static void renesas_i3c_hw_init(struct renesas_i3c *i3c)
|
||||
{
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct i3c_bus *bus = i3c_master_get_bus(m);
|
||||
struct i3c_device_info info = {};
|
||||
struct i2c_timings t;
|
||||
unsigned long rate;
|
||||
u32 double_SBR, val;
|
||||
int cks, pp_high_ticks, pp_low_ticks, i3c_total_ticks;
|
||||
int od_high_ticks, od_low_ticks, i2c_total_ticks;
|
||||
int ret;
|
||||
|
||||
rate = clk_get_rate(i3c->tclk);
|
||||
if (!rate)
|
||||
return -EINVAL;
|
||||
|
||||
ret = renesas_i3c_reset(i3c);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i2c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i2c);
|
||||
i3c_total_ticks = DIV_ROUND_UP(rate, bus->scl_rate.i3c);
|
||||
|
||||
i2c_parse_fw_timings(&m->dev, &t, true);
|
||||
|
||||
for (cks = 0; cks < 7; cks++) {
|
||||
/* SCL low-period calculation in Open-drain mode */
|
||||
od_low_ticks = ((i2c_total_ticks * 6) / 10);
|
||||
|
||||
/* SCL clock calculation in Push-Pull mode */
|
||||
if (bus->mode == I3C_BUS_MODE_PURE)
|
||||
pp_high_ticks = ((i3c_total_ticks * 5) / 10);
|
||||
else
|
||||
pp_high_ticks = DIV_ROUND_UP(I3C_BUS_THIGH_MIXED_MAX_NS,
|
||||
NSEC_PER_SEC / rate);
|
||||
pp_low_ticks = i3c_total_ticks - pp_high_ticks;
|
||||
|
||||
if ((od_low_ticks / 2) <= 0xFF && pp_low_ticks < 0x3F)
|
||||
break;
|
||||
|
||||
i2c_total_ticks /= 2;
|
||||
i3c_total_ticks /= 2;
|
||||
rate /= 2;
|
||||
}
|
||||
|
||||
/* SCL clock period calculation in Open-drain mode */
|
||||
if ((od_low_ticks / 2) > 0xFF || pp_low_ticks > 0x3F) {
|
||||
dev_err(&m->dev, "invalid speed (i2c-scl = %lu Hz, i3c-scl = %lu Hz). Too slow.\n",
|
||||
(unsigned long)bus->scl_rate.i2c, (unsigned long)bus->scl_rate.i3c);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* SCL high-period calculation in Open-drain mode */
|
||||
od_high_ticks = i2c_total_ticks - od_low_ticks;
|
||||
|
||||
/* Standard Bit Rate setting */
|
||||
double_SBR = od_low_ticks > 0xFF ? 1 : 0;
|
||||
i3c->i3c_STDBR = (double_SBR ? STDBR_DSBRPO : 0) |
|
||||
STDBR_SBRLO(double_SBR, od_low_ticks) |
|
||||
STDBR_SBRHO(double_SBR, od_high_ticks) |
|
||||
STDBR_SBRLP(pp_low_ticks) |
|
||||
STDBR_SBRHP(pp_high_ticks);
|
||||
|
||||
od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / rate) + 1;
|
||||
od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / rate) + 1;
|
||||
i3c->i2c_STDBR = (double_SBR ? STDBR_DSBRPO : 0) |
|
||||
STDBR_SBRLO(double_SBR, od_low_ticks) |
|
||||
STDBR_SBRHO(double_SBR, od_high_ticks) |
|
||||
STDBR_SBRLP(pp_low_ticks) |
|
||||
STDBR_SBRHP(pp_high_ticks);
|
||||
renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
|
||||
|
||||
/* Extended Bit Rate setting */
|
||||
renesas_writel(i3c->regs, EXTBR, EXTBR_EBRLO(od_low_ticks) |
|
||||
EXTBR_EBRHO(od_high_ticks) |
|
||||
EXTBR_EBRLP(pp_low_ticks) |
|
||||
EXTBR_EBRHP(pp_high_ticks));
|
||||
|
||||
renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
|
||||
u32 val;
|
||||
|
||||
/* Disable Slave Mode */
|
||||
renesas_writel(i3c->regs, SVCTL, 0);
|
||||
|
||||
/* Initialize Queue/Buffer threshold */
|
||||
renesas_writel(i3c->regs, NQTHCTL, NQTHCTL_IBIDSSZ(6) |
|
||||
NQTHCTL_CMDQTH(1));
|
||||
NQTHCTL_CMDQTH(1));
|
||||
|
||||
/* The only supported configuration is two entries*/
|
||||
renesas_writel(i3c->regs, NTBTHCTL0, 0);
|
||||
|
|
@ -586,25 +515,113 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
|
|||
renesas_set_bit(i3c->regs, BCTL, BCTL_HJACKCTL);
|
||||
|
||||
renesas_writel(i3c->regs, IBINCTL, IBINCTL_NRHJCTL | IBINCTL_NRMRCTL |
|
||||
IBINCTL_NRSIRCTL);
|
||||
IBINCTL_NRSIRCTL);
|
||||
|
||||
renesas_writel(i3c->regs, SCSTLCTL, 0);
|
||||
renesas_set_bit(i3c->regs, SCSTRCTL, SCSTRCTL_ACKTWE);
|
||||
|
||||
/* Bus condition timing */
|
||||
val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS, NSEC_PER_SEC / rate);
|
||||
val = DIV_ROUND_UP(I3C_BUS_TBUF_MIXED_FM_MIN_NS,
|
||||
NSEC_PER_SEC / i3c->rate);
|
||||
renesas_writel(i3c->regs, BFRECDT, BFRECDT_FRECYC(val));
|
||||
|
||||
val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS, NSEC_PER_SEC / rate);
|
||||
val = DIV_ROUND_UP(I3C_BUS_TAVAL_MIN_NS,
|
||||
NSEC_PER_SEC / i3c->rate);
|
||||
renesas_writel(i3c->regs, BAVLCDT, BAVLCDT_AVLCYC(val));
|
||||
|
||||
val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS, NSEC_PER_SEC / rate);
|
||||
val = DIV_ROUND_UP(I3C_BUS_TIDLE_MIN_NS,
|
||||
NSEC_PER_SEC / i3c->rate);
|
||||
renesas_writel(i3c->regs, BIDLCDT, BIDLCDT_IDLCYC(val));
|
||||
}
|
||||
|
||||
static int renesas_i3c_bus_init(struct i3c_master_controller *m)
|
||||
{
|
||||
struct renesas_i3c *i3c = to_renesas_i3c(m);
|
||||
struct i3c_bus *bus = i3c_master_get_bus(m);
|
||||
struct i3c_device_info info = {};
|
||||
struct i2c_timings t;
|
||||
u32 double_SBR;
|
||||
int cks, pp_high_ticks, pp_low_ticks, i3c_total_ticks;
|
||||
int od_high_ticks, od_low_ticks, i2c_total_ticks;
|
||||
int ret;
|
||||
|
||||
i3c->rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk);
|
||||
if (!i3c->rate)
|
||||
return -EINVAL;
|
||||
|
||||
ret = renesas_i3c_reset(i3c);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
i2c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i2c);
|
||||
i3c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i3c);
|
||||
|
||||
i2c_parse_fw_timings(&m->dev, &t, true);
|
||||
|
||||
for (cks = 0; cks < 7; cks++) {
|
||||
/* SCL low-period calculation in Open-drain mode */
|
||||
od_low_ticks = ((i2c_total_ticks * 6) / 10);
|
||||
|
||||
/* SCL clock calculation in Push-Pull mode */
|
||||
if (bus->mode == I3C_BUS_MODE_PURE)
|
||||
pp_high_ticks = ((i3c_total_ticks * 5) / 10);
|
||||
else
|
||||
pp_high_ticks = DIV_ROUND_UP(I3C_BUS_THIGH_MIXED_MAX_NS,
|
||||
NSEC_PER_SEC / i3c->rate);
|
||||
pp_low_ticks = i3c_total_ticks - pp_high_ticks;
|
||||
|
||||
if ((od_low_ticks / 2) <= 0xFF && pp_low_ticks < 0x3F)
|
||||
break;
|
||||
|
||||
i2c_total_ticks /= 2;
|
||||
i3c_total_ticks /= 2;
|
||||
i3c->rate /= 2;
|
||||
}
|
||||
|
||||
/* SCL clock period calculation in Open-drain mode */
|
||||
if ((od_low_ticks / 2) > 0xFF || pp_low_ticks > 0x3F) {
|
||||
dev_err(&m->dev, "invalid speed (i2c-scl = %lu Hz, i3c-scl = %lu Hz). Too slow.\n",
|
||||
(unsigned long)bus->scl_rate.i2c, (unsigned long)bus->scl_rate.i3c);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* SCL high-period calculation in Open-drain mode */
|
||||
od_high_ticks = i2c_total_ticks - od_low_ticks;
|
||||
|
||||
/* Standard Bit Rate setting */
|
||||
double_SBR = od_low_ticks > 0xFF ? 1 : 0;
|
||||
i3c->i3c_STDBR = (double_SBR ? STDBR_DSBRPO : 0) |
|
||||
STDBR_SBRLO(double_SBR, od_low_ticks) |
|
||||
STDBR_SBRHO(double_SBR, od_high_ticks) |
|
||||
STDBR_SBRLP(pp_low_ticks) |
|
||||
STDBR_SBRHP(pp_high_ticks);
|
||||
|
||||
od_low_ticks -= t.scl_fall_ns / (NSEC_PER_SEC / i3c->rate) + 1;
|
||||
od_high_ticks -= t.scl_rise_ns / (NSEC_PER_SEC / i3c->rate) + 1;
|
||||
i3c->i2c_STDBR = (double_SBR ? STDBR_DSBRPO : 0) |
|
||||
STDBR_SBRLO(double_SBR, od_low_ticks) |
|
||||
STDBR_SBRHO(double_SBR, od_high_ticks) |
|
||||
STDBR_SBRLP(pp_low_ticks) |
|
||||
STDBR_SBRHP(pp_high_ticks);
|
||||
renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
|
||||
|
||||
/* Extended Bit Rate setting */
|
||||
renesas_writel(i3c->regs, EXTBR, EXTBR_EBRLO(od_low_ticks) |
|
||||
EXTBR_EBRHO(od_high_ticks) |
|
||||
EXTBR_EBRLP(pp_low_ticks) |
|
||||
EXTBR_EBRHP(pp_high_ticks));
|
||||
|
||||
renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
|
||||
i3c->refclk_div = cks;
|
||||
|
||||
/* I3C hw init*/
|
||||
renesas_i3c_hw_init(i3c);
|
||||
|
||||
ret = i3c_master_get_free_addr(m, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
i3c->dyn_addr = ret;
|
||||
renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
|
|
@ -1301,14 +1318,8 @@ static const struct renesas_i3c_irq_desc renesas_i3c_irqs[] = {
|
|||
static int renesas_i3c_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct renesas_i3c *i3c;
|
||||
struct reset_control *reset;
|
||||
struct clk *clk;
|
||||
const struct renesas_i3c_config *config = of_device_get_match_data(&pdev->dev);
|
||||
int ret, i;
|
||||
|
||||
if (!config)
|
||||
return -ENODATA;
|
||||
|
||||
i3c = devm_kzalloc(&pdev->dev, sizeof(*i3c), GFP_KERNEL);
|
||||
if (!i3c)
|
||||
return -ENOMEM;
|
||||
|
|
@ -1317,28 +1328,21 @@ static int renesas_i3c_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(i3c->regs))
|
||||
return PTR_ERR(i3c->regs);
|
||||
|
||||
clk = devm_clk_get_enabled(&pdev->dev, "pclk");
|
||||
if (IS_ERR(clk))
|
||||
return PTR_ERR(clk);
|
||||
ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &i3c->clks);
|
||||
if (ret <= RENESAS_I3C_TCLK_IDX)
|
||||
return dev_err_probe(&pdev->dev, ret < 0 ? ret : -EINVAL,
|
||||
"Failed to get clocks (need > %d, got %d)\n",
|
||||
RENESAS_I3C_TCLK_IDX, ret);
|
||||
i3c->num_clks = ret;
|
||||
|
||||
if (config->has_pclkrw) {
|
||||
clk = devm_clk_get_enabled(&pdev->dev, "pclkrw");
|
||||
if (IS_ERR(clk))
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
i3c->tclk = devm_clk_get_enabled(&pdev->dev, "tclk");
|
||||
if (IS_ERR(i3c->tclk))
|
||||
return PTR_ERR(i3c->tclk);
|
||||
|
||||
reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
|
||||
if (IS_ERR(reset))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(reset),
|
||||
i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
|
||||
if (IS_ERR(i3c->tresetn))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tresetn),
|
||||
"Error: missing tresetn ctrl\n");
|
||||
|
||||
reset = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn");
|
||||
if (IS_ERR(reset))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(reset),
|
||||
i3c->presetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "presetn");
|
||||
if (IS_ERR(i3c->presetn))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(i3c->presetn),
|
||||
"Error: missing presetn ctrl\n");
|
||||
|
||||
spin_lock_init(&i3c->xferqueue.lock);
|
||||
|
|
@ -1364,6 +1368,12 @@ static int renesas_i3c_probe(struct platform_device *pdev)
|
|||
i3c->maxdevs = RENESAS_I3C_MAX_DEVS;
|
||||
i3c->free_pos = GENMASK(i3c->maxdevs - 1, 0);
|
||||
|
||||
/* Allocate dynamic Device Address Table backup. */
|
||||
i3c->DATBASn = devm_kzalloc(&pdev->dev, sizeof(u32) * i3c->maxdevs,
|
||||
GFP_KERNEL);
|
||||
if (!i3c->DATBASn)
|
||||
return -ENOMEM;
|
||||
|
||||
return i3c_master_register(&i3c->base, &pdev->dev, &renesas_i3c_ops, false);
|
||||
}
|
||||
|
||||
|
|
@ -1374,16 +1384,86 @@ static void renesas_i3c_remove(struct platform_device *pdev)
|
|||
i3c_master_unregister(&i3c->base);
|
||||
}
|
||||
|
||||
static const struct renesas_i3c_config empty_i3c_config = {
|
||||
};
|
||||
static int renesas_i3c_suspend_noirq(struct device *dev)
|
||||
{
|
||||
struct renesas_i3c *i3c = dev_get_drvdata(dev);
|
||||
int i, ret;
|
||||
|
||||
static const struct renesas_i3c_config r9a09g047_i3c_config = {
|
||||
.has_pclkrw = 1,
|
||||
i2c_mark_adapter_suspended(&i3c->base.i2c);
|
||||
|
||||
/* Store Device Address Table values. */
|
||||
for (i = 0; i < i3c->maxdevs; i++)
|
||||
i3c->DATBASn[i] = renesas_readl(i3c->regs, DATBAS(i));
|
||||
|
||||
ret = reset_control_assert(i3c->presetn);
|
||||
if (ret)
|
||||
goto err_mark_resumed;
|
||||
|
||||
ret = reset_control_assert(i3c->tresetn);
|
||||
if (ret)
|
||||
goto err_presetn;
|
||||
|
||||
clk_bulk_disable(i3c->num_clks, i3c->clks);
|
||||
|
||||
return 0;
|
||||
|
||||
err_presetn:
|
||||
reset_control_deassert(i3c->presetn);
|
||||
err_mark_resumed:
|
||||
i2c_mark_adapter_resumed(&i3c->base.i2c);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int renesas_i3c_resume_noirq(struct device *dev)
|
||||
{
|
||||
struct renesas_i3c *i3c = dev_get_drvdata(dev);
|
||||
int i, ret;
|
||||
|
||||
ret = reset_control_deassert(i3c->presetn);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = reset_control_deassert(i3c->tresetn);
|
||||
if (ret)
|
||||
goto err_presetn;
|
||||
|
||||
ret = clk_bulk_enable(i3c->num_clks, i3c->clks);
|
||||
if (ret)
|
||||
goto err_tresetn;
|
||||
|
||||
/* Re-store I3C registers value. */
|
||||
renesas_writel(i3c->regs, REFCKCTL,
|
||||
REFCKCTL_IREFCKS(i3c->refclk_div));
|
||||
renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYADV |
|
||||
MSDVAD_MDYAD(i3c->dyn_addr));
|
||||
|
||||
/* Restore Device Address Table values. */
|
||||
for (i = 0; i < i3c->maxdevs; i++)
|
||||
renesas_writel(i3c->regs, DATBAS(i), i3c->DATBASn[i]);
|
||||
|
||||
/* I3C hw init. */
|
||||
renesas_i3c_hw_init(i3c);
|
||||
|
||||
i2c_mark_adapter_resumed(&i3c->base.i2c);
|
||||
|
||||
return 0;
|
||||
|
||||
err_tresetn:
|
||||
reset_control_assert(i3c->tresetn);
|
||||
err_presetn:
|
||||
reset_control_assert(i3c->presetn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops renesas_i3c_pm_ops = {
|
||||
NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq,
|
||||
renesas_i3c_resume_noirq)
|
||||
};
|
||||
|
||||
static const struct of_device_id renesas_i3c_of_ids[] = {
|
||||
{ .compatible = "renesas,r9a08g045-i3c", .data = &empty_i3c_config },
|
||||
{ .compatible = "renesas,r9a09g047-i3c", .data = &r9a09g047_i3c_config },
|
||||
{ .compatible = "renesas,r9a08g045-i3c" },
|
||||
{ .compatible = "renesas,r9a09g047-i3c" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, renesas_i3c_of_ids);
|
||||
|
|
@ -1394,6 +1474,7 @@ static struct platform_driver renesas_i3c = {
|
|||
.driver = {
|
||||
.name = "renesas-i3c",
|
||||
.of_match_table = renesas_i3c_of_ids,
|
||||
.pm = pm_sleep_ptr(&renesas_i3c_pm_ops),
|
||||
},
|
||||
};
|
||||
module_platform_driver(renesas_i3c);
|
||||
|
|
|
|||
|
|
@ -533,8 +533,8 @@ static int svc_i3c_master_handle_ibi_won(struct svc_i3c_master *master, u32 msta
|
|||
static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master)
|
||||
{
|
||||
struct svc_i3c_i2c_dev_data *data;
|
||||
struct i3c_dev_desc *dev = NULL;
|
||||
unsigned int ibitype, ibiaddr;
|
||||
struct i3c_dev_desc *dev;
|
||||
u32 status, val;
|
||||
int ret;
|
||||
|
||||
|
|
@ -627,7 +627,7 @@ static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master)
|
|||
* for the slave to interrupt again.
|
||||
*/
|
||||
if (svc_i3c_master_error(master)) {
|
||||
if (master->ibi.tbq_slot) {
|
||||
if (master->ibi.tbq_slot && dev) {
|
||||
data = i3c_dev_get_master_data(dev);
|
||||
i3c_generic_ibi_recycle_slot(data->ibi_pool,
|
||||
master->ibi.tbq_slot);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
* @I3C_ERROR_M2: M2 error
|
||||
*
|
||||
* These are the standard error codes as defined by the I3C specification.
|
||||
* When -EIO is returned by the i3c_device_do_priv_xfers() or
|
||||
* When -EIO is returned by the i3c_device_do_i3c_xfers() or
|
||||
* i3c_device_send_hdr_cmds() one can check the error code in
|
||||
* &struct_i3c_xfer.err or &struct i3c_hdr_cmd.err to get a better idea of
|
||||
* what went wrong.
|
||||
|
|
@ -79,9 +79,6 @@ struct i3c_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
|
||||
|
|
@ -308,16 +305,24 @@ static __always_inline void i3c_i2c_driver_unregister(struct i3c_driver *i3cdrv,
|
|||
i3c_i2c_driver_unregister, \
|
||||
__i2cdrv)
|
||||
|
||||
#if IS_ENABLED(CONFIG_I3C)
|
||||
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)
|
||||
u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev);
|
||||
#else
|
||||
static inline int
|
||||
i3c_device_do_xfers(struct i3c_device *dev, struct i3c_xfer *xfers,
|
||||
int nxfers, enum i3c_xfer_mode mode)
|
||||
{
|
||||
return i3c_device_do_xfers(dev, xfers, nxfers, I3C_SDR);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int i3c_device_do_setdasa(struct i3c_device *dev);
|
||||
|
||||
void i3c_device_get_info(const struct i3c_device *dev, struct i3c_device_info *info);
|
||||
|
|
@ -358,6 +363,5 @@ 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 */
|
||||
|
|
|
|||
|
|
@ -462,6 +462,8 @@ struct i3c_bus {
|
|||
* @enable_hotjoin: enable hot join event detect.
|
||||
* @disable_hotjoin: disable hot join event detect.
|
||||
* @set_speed: adjust I3C open drain mode timing.
|
||||
* @set_dev_nack_retry: configure device NACK retry count for the master
|
||||
* controller.
|
||||
*/
|
||||
struct i3c_master_controller_ops {
|
||||
int (*bus_init)(struct i3c_master_controller *master);
|
||||
|
|
@ -491,6 +493,8 @@ struct i3c_master_controller_ops {
|
|||
int (*enable_hotjoin)(struct i3c_master_controller *master);
|
||||
int (*disable_hotjoin)(struct i3c_master_controller *master);
|
||||
int (*set_speed)(struct i3c_master_controller *master, enum i3c_open_drain_speed speed);
|
||||
int (*set_dev_nack_retry)(struct i3c_master_controller *master,
|
||||
unsigned long dev_nack_retry_cnt);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -505,6 +509,8 @@ struct i3c_master_controller_ops {
|
|||
* @secondary: true if the master is a secondary master
|
||||
* @init_done: true when the bus initialization is done
|
||||
* @hotjoin: true if the master support hotjoin
|
||||
* @rpm_allowed: true if Runtime PM allowed
|
||||
* @rpm_ibi_allowed: true if IBI and Hot-Join allowed while runtime suspended
|
||||
* @boardinfo.i3c: list of I3C boardinfo objects
|
||||
* @boardinfo.i2c: list of I2C boardinfo objects
|
||||
* @boardinfo: board-level information attached to devices connected on the bus
|
||||
|
|
@ -514,6 +520,7 @@ struct i3c_master_controller_ops {
|
|||
* in a thread context. Typical examples are Hot Join processing which
|
||||
* requires taking the bus lock in maintenance, which in turn, can only
|
||||
* be done from a sleep-able context
|
||||
* @dev_nack_retry_count: retry count when slave device nack
|
||||
*
|
||||
* A &struct i3c_master_controller has to be registered to the I3C subsystem
|
||||
* through i3c_master_register(). None of &struct i3c_master_controller fields
|
||||
|
|
@ -528,12 +535,15 @@ struct i3c_master_controller {
|
|||
unsigned int secondary : 1;
|
||||
unsigned int init_done : 1;
|
||||
unsigned int hotjoin: 1;
|
||||
unsigned int rpm_allowed: 1;
|
||||
unsigned int rpm_ibi_allowed: 1;
|
||||
struct {
|
||||
struct list_head i3c;
|
||||
struct list_head i2c;
|
||||
} boardinfo;
|
||||
struct i3c_bus bus;
|
||||
struct workqueue_struct *wq;
|
||||
unsigned int dev_nack_retry_count;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -595,6 +605,7 @@ int i3c_master_get_free_addr(struct i3c_master_controller *master,
|
|||
int i3c_master_add_i3c_dev_locked(struct i3c_master_controller *master,
|
||||
u8 addr);
|
||||
int i3c_master_do_daa(struct i3c_master_controller *master);
|
||||
int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa);
|
||||
struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *ptr,
|
||||
size_t len, bool force_bounce,
|
||||
enum dma_data_direction dir);
|
||||
|
|
|
|||
15
include/linux/platform_data/mipi-i3c-hci.h
Normal file
15
include/linux/platform_data/mipi-i3c-hci.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef INCLUDE_PLATFORM_DATA_MIPI_I3C_HCI_H
|
||||
#define INCLUDE_PLATFORM_DATA_MIPI_I3C_HCI_H
|
||||
|
||||
#include <linux/compiler_types.h>
|
||||
|
||||
/**
|
||||
* struct mipi_i3c_hci_platform_data - Platform-dependent data for mipi_i3c_hci
|
||||
* @base_regs: Register set base address (to support multi-bus instances)
|
||||
*/
|
||||
struct mipi_i3c_hci_platform_data {
|
||||
void __iomem *base_regs;
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue