Commit 529766e0 authored by Elie Morisse's avatar Elie Morisse Committed by Wolfram Sang

i2c: Add drivers for the AMD PCIe MP2 I2C controller

MP2 controllers have two separate busses, so may accommodate up to two I2C
adapters. Those adapters are listed in the ACPI namespace with the
"AMDI0011" HID, and probed by a platform driver.

Communication with the MP2 takes place through MMIO registers, or through
DMA for more than 32 bytes transfers.

This is major rework of the patch submitted by Nehal-bakulchandra Shah from
AMD (https://patchwork.kernel.org/patch/10597369/).

Most of the event handling of v3 was rewritten to make it work with more
than one bus (e.g on Ryzen-based Lenovo Yoga 530), and this version
contains many other improvements.
Signed-off-by: default avatarElie Morisse <syniurge@gmail.com>
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
parent 36ea73cb
Kernel driver i2c-amd-mp2
Supported adapters:
* AMD MP2 PCIe interface
Datasheet: not publicly available.
Authors:
Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
Nehal Shah <nehal-bakulchandra.shah@amd.com>
Elie Morisse <syniurge@gmail.com>
Description
-----------
The MP2 is an ARM processor programmed as an I2C controller and communicating
with the x86 host through PCI.
If you see something like this:
03:00.7 MP2 I2C controller: Advanced Micro Devices, Inc. [AMD] Device 15e6
in your 'lspci -v', then this driver is for your device.
......@@ -816,6 +816,14 @@ F: drivers/gpu/drm/amd/include/vi_structs.h
F: drivers/gpu/drm/amd/include/v9_structs.h
F: include/uapi/linux/kfd_ioctl.h
AMD MP2 I2C DRIVER
M: Elie Morisse <syniurge@gmail.com>
M: Nehal Shah <nehal-bakulchandra.shah@amd.com>
M: Shyam Sundar S K <shyam-sundar.s-k@amd.com>
L: linux-i2c@vger.kernel.org
S: Maintained
F: drivers/i2c/busses/i2c-amd-mp2*
AMD POWERPLAY
M: Rex Zhu <rex.zhu@amd.com>
M: Evan Quan <evan.quan@amd.com>
......
......@@ -77,6 +77,16 @@ config I2C_AMD8111
This driver can also be built as a module. If so, the module
will be called i2c-amd8111.
config I2C_AMD_MP2
tristate "AMD MP2 PCIe"
depends on PCI && ACPI
help
If you say yes to this option, support will be included for the AMD
MP2 PCIe I2C adapter.
This driver can also be built as modules. If so, the modules will
be called i2c-amd-mp2-pci and i2c-amd-mp2-plat.
config I2C_HIX5HD2
tristate "Hix5hd2 high-speed I2C driver"
depends on ARCH_HISI || ARCH_HIX5HD2 || COMPILE_TEST
......
......@@ -33,6 +33,7 @@ obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o
# Embedded system I2C/SMBus host controller drivers
obj-$(CONFIG_I2C_ALTERA) += i2c-altera.o
obj-$(CONFIG_I2C_AMD_MP2) += i2c-amd-mp2-pci.o i2c-amd-mp2-plat.o
obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o
obj-$(CONFIG_I2C_AT91) += i2c-at91.o
i2c-at91-objs := i2c-at91-core.o i2c-at91-master.o
......
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* AMD MP2 platform driver
*
* Setup the I2C adapters enumerated in the ACPI namespace.
* MP2 controllers have 2 separate busses, up to 2 I2C adapters may be listed.
*
* Authors: Nehal Bakulchandra Shah <Nehal-bakulchandra.shah@amd.com>
* Elie Morisse <syniurge@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "i2c-amd-mp2.h"
#define AMD_MP2_I2C_MAX_RW_LENGTH ((1 << 12) - 1)
#define AMD_I2C_TIMEOUT (msecs_to_jiffies(250))
/**
* struct amd_i2c_dev - MP2 bus/i2c adapter context
* @common: shared context with the MP2 PCI driver
* @pdev: platform driver node
* @adap: i2c adapter
* @cmd_complete: xfer completion object
*/
struct amd_i2c_dev {
struct amd_i2c_common common;
struct platform_device *pdev;
struct i2c_adapter adap;
struct completion cmd_complete;
};
#define amd_i2c_dev_common(__common) \
container_of(__common, struct amd_i2c_dev, common)
static int i2c_amd_dma_map(struct amd_i2c_common *i2c_common)
{
struct device *dev_pci = &i2c_common->mp2_dev->pci_dev->dev;
struct amd_i2c_dev *i2c_dev = amd_i2c_dev_common(i2c_common);
enum dma_data_direction dma_direction =
i2c_common->msg->flags & I2C_M_RD ?
DMA_FROM_DEVICE : DMA_TO_DEVICE;
i2c_common->dma_buf = i2c_get_dma_safe_msg_buf(i2c_common->msg, 0);
i2c_common->dma_addr = dma_map_single(dev_pci, i2c_common->dma_buf,
i2c_common->msg->len,
dma_direction);
if (unlikely(dma_mapping_error(dev_pci, i2c_common->dma_addr))) {
dev_err(&i2c_dev->pdev->dev,
"Error while mapping dma buffer %p\n",
i2c_common->dma_buf);
return -EIO;
}
return 0;
}
static void i2c_amd_dma_unmap(struct amd_i2c_common *i2c_common)
{
struct device *dev_pci = &i2c_common->mp2_dev->pci_dev->dev;
enum dma_data_direction dma_direction =
i2c_common->msg->flags & I2C_M_RD ?
DMA_FROM_DEVICE : DMA_TO_DEVICE;
dma_unmap_single(dev_pci, i2c_common->dma_addr,
i2c_common->msg->len, dma_direction);
i2c_put_dma_safe_msg_buf(i2c_common->dma_buf, i2c_common->msg, true);
}
static void i2c_amd_start_cmd(struct amd_i2c_dev *i2c_dev)
{
struct amd_i2c_common *i2c_common = &i2c_dev->common;
reinit_completion(&i2c_dev->cmd_complete);
i2c_common->cmd_success = false;
}
static void i2c_amd_cmd_completion(struct amd_i2c_common *i2c_common)
{
struct amd_i2c_dev *i2c_dev = amd_i2c_dev_common(i2c_common);
union i2c_event *event = &i2c_common->eventval;
if (event->r.status == i2c_readcomplete_event)
dev_dbg(&i2c_dev->pdev->dev, "%s readdata:%*ph\n",
__func__, event->r.length,
i2c_common->msg->buf);
complete(&i2c_dev->cmd_complete);
}
static int i2c_amd_check_cmd_completion(struct amd_i2c_dev *i2c_dev)
{
struct amd_i2c_common *i2c_common = &i2c_dev->common;
unsigned long timeout;
timeout = wait_for_completion_timeout(&i2c_dev->cmd_complete,
i2c_dev->adap.timeout);
if ((i2c_common->reqcmd == i2c_read ||
i2c_common->reqcmd == i2c_write) &&
i2c_common->msg->len > 32)
i2c_amd_dma_unmap(i2c_common);
if (timeout == 0) {
amd_mp2_rw_timeout(i2c_common);
return -ETIMEDOUT;
}
amd_mp2_process_event(i2c_common);
if (!i2c_common->cmd_success)
return -EIO;
return 0;
}
static int i2c_amd_enable_set(struct amd_i2c_dev *i2c_dev, bool enable)
{
struct amd_i2c_common *i2c_common = &i2c_dev->common;
i2c_amd_start_cmd(i2c_dev);
amd_mp2_bus_enable_set(i2c_common, enable);
return i2c_amd_check_cmd_completion(i2c_dev);
}
static int i2c_amd_xfer_msg(struct amd_i2c_dev *i2c_dev, struct i2c_msg *pmsg)
{
struct amd_i2c_common *i2c_common = &i2c_dev->common;
i2c_amd_start_cmd(i2c_dev);
i2c_common->msg = pmsg;
if (pmsg->len > 32)
if (i2c_amd_dma_map(i2c_common))
return -EIO;
if (pmsg->flags & I2C_M_RD)
amd_mp2_rw(i2c_common, i2c_read);
else
amd_mp2_rw(i2c_common, i2c_write);
return i2c_amd_check_cmd_completion(i2c_dev);
}
static int i2c_amd_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
struct amd_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
int i;
struct i2c_msg *pmsg;
int err;
/* the adapter might have been deleted while waiting for the bus lock */
if (unlikely(!i2c_dev->common.mp2_dev))
return -EINVAL;
amd_mp2_pm_runtime_get(i2c_dev->common.mp2_dev);
for (i = 0; i < num; i++) {
pmsg = &msgs[i];
err = i2c_amd_xfer_msg(i2c_dev, pmsg);
if (err)
break;
}
amd_mp2_pm_runtime_put(i2c_dev->common.mp2_dev);
return err ? err : num;
}
static u32 i2c_amd_func(struct i2c_adapter *a)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm i2c_amd_algorithm = {
.master_xfer = i2c_amd_xfer,
.functionality = i2c_amd_func,
};
#ifdef CONFIG_PM
static int i2c_amd_suspend(struct amd_i2c_common *i2c_common)
{
struct amd_i2c_dev *i2c_dev = amd_i2c_dev_common(i2c_common);
i2c_amd_enable_set(i2c_dev, false);
return 0;
}
static int i2c_amd_resume(struct amd_i2c_common *i2c_common)
{
struct amd_i2c_dev *i2c_dev = amd_i2c_dev_common(i2c_common);
return i2c_amd_enable_set(i2c_dev, true);
}
#endif
static enum speed_enum i2c_amd_get_bus_speed(struct platform_device *pdev)
{
u32 acpi_speed;
int i;
static const u32 supported_speeds[] = {
0, 100000, 400000, 1000000, 1400000, 3400000
};
acpi_speed = i2c_acpi_find_bus_speed(&pdev->dev);
/* round down to the lowest standard speed */
for (i = 1; i < ARRAY_SIZE(supported_speeds); i++) {
if (acpi_speed < supported_speeds[i])
break;
}
acpi_speed = supported_speeds[i - 1];
switch (acpi_speed) {
case 100000:
return speed100k;
case 400000:
return speed400k;
case 1000000:
return speed1000k;
case 1400000:
return speed1400k;
case 3400000:
return speed3400k;
default:
return speed400k;
}
}
static const struct i2c_adapter_quirks amd_i2c_dev_quirks = {
.max_read_len = AMD_MP2_I2C_MAX_RW_LENGTH,
.max_write_len = AMD_MP2_I2C_MAX_RW_LENGTH,
};
static int i2c_amd_probe(struct platform_device *pdev)
{
int ret;
struct amd_i2c_dev *i2c_dev;
acpi_handle handle = ACPI_HANDLE(&pdev->dev);
struct acpi_device *adev;
struct amd_mp2_dev *mp2_dev;
const char *uid;
if (acpi_bus_get_device(handle, &adev))
return -ENODEV;
/* The ACPI namespace doesn't contain information about which MP2 PCI
* device an AMDI0011 ACPI device is related to, so assume that there's
* only one MP2 PCI device per system.
*/
mp2_dev = amd_mp2_find_device();
if (!mp2_dev || !mp2_dev->probed)
/* The MP2 PCI device should get probed later */
return -EPROBE_DEFER;
i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);
if (!i2c_dev)
return -ENOMEM;
i2c_dev->common.mp2_dev = mp2_dev;
i2c_dev->pdev = pdev;
platform_set_drvdata(pdev, i2c_dev);
i2c_dev->common.cmd_completion = &i2c_amd_cmd_completion;
#ifdef CONFIG_PM
i2c_dev->common.suspend = &i2c_amd_suspend;
i2c_dev->common.resume = &i2c_amd_resume;
#endif
uid = adev->pnp.unique_id;
if (!uid) {
dev_err(&pdev->dev, "missing UID/bus id!\n");
return -EINVAL;
} else if (strcmp(uid, "0") == 0) {
i2c_dev->common.bus_id = 0;
} else if (strcmp(uid, "1") == 0) {
i2c_dev->common.bus_id = 1;
} else {
dev_err(&pdev->dev, "incorrect UID/bus id \"%s\"!\n", uid);
return -EINVAL;
}
dev_dbg(&pdev->dev, "bus id is %u\n", i2c_dev->common.bus_id);
/* Register the adapter */
amd_mp2_pm_runtime_get(mp2_dev);
i2c_dev->common.reqcmd = i2c_none;
if (amd_mp2_register_cb(&i2c_dev->common))
return -EINVAL;
device_link_add(&i2c_dev->pdev->dev, &mp2_dev->pci_dev->dev,
DL_FLAG_AUTOREMOVE_CONSUMER);
i2c_dev->common.i2c_speed = i2c_amd_get_bus_speed(pdev);
/* Setup i2c adapter description */
i2c_dev->adap.owner = THIS_MODULE;
i2c_dev->adap.algo = &i2c_amd_algorithm;
i2c_dev->adap.quirks = &amd_i2c_dev_quirks;
i2c_dev->adap.dev.parent = &pdev->dev;
i2c_dev->adap.algo_data = i2c_dev;
i2c_dev->adap.timeout = AMD_I2C_TIMEOUT;
ACPI_COMPANION_SET(&i2c_dev->adap.dev, ACPI_COMPANION(&pdev->dev));
i2c_dev->adap.dev.of_node = pdev->dev.of_node;
snprintf(i2c_dev->adap.name, sizeof(i2c_dev->adap.name),
"AMD MP2 i2c bus %u", i2c_dev->common.bus_id);
i2c_set_adapdata(&i2c_dev->adap, i2c_dev);
init_completion(&i2c_dev->cmd_complete);
/* Enable the bus */
if (i2c_amd_enable_set(i2c_dev, true))
dev_err(&pdev->dev, "initial bus enable failed\n");
/* Attach to the i2c layer */
ret = i2c_add_adapter(&i2c_dev->adap);
amd_mp2_pm_runtime_put(mp2_dev);
if (ret < 0)
dev_err(&pdev->dev, "i2c add adapter failed = %d\n", ret);
return ret;
}
static int i2c_amd_remove(struct platform_device *pdev)
{
struct amd_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
struct amd_i2c_common *i2c_common = &i2c_dev->common;
i2c_lock_bus(&i2c_dev->adap, I2C_LOCK_ROOT_ADAPTER);
i2c_amd_enable_set(i2c_dev, false);
amd_mp2_unregister_cb(i2c_common);
i2c_common->mp2_dev = NULL;
i2c_unlock_bus(&i2c_dev->adap, I2C_LOCK_ROOT_ADAPTER);
i2c_del_adapter(&i2c_dev->adap);
return 0;
}
static const struct acpi_device_id i2c_amd_acpi_match[] = {
{ "AMDI0011" },
{ },
};
MODULE_DEVICE_TABLE(acpi, i2c_amd_acpi_match);
static struct platform_driver i2c_amd_plat_driver = {
.probe = i2c_amd_probe,
.remove = i2c_amd_remove,
.driver = {
.name = "i2c_amd_mp2",
.acpi_match_table = ACPI_PTR(i2c_amd_acpi_match),
},
};
module_platform_driver(i2c_amd_plat_driver);
MODULE_DESCRIPTION("AMD(R) MP2 I2C Platform Driver");
MODULE_AUTHOR("Nehal Shah <nehal-bakulchandra.shah@amd.com>");
MODULE_AUTHOR("Elie Morisse <syniurge@gmail.com>");
MODULE_LICENSE("Dual BSD/GPL");
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* AMD MP2 I2C adapter driver
*
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
* Elie Morisse <syniurge@gmail.com>
*/
#ifndef I2C_AMD_PCI_MP2_H
#define I2C_AMD_PCI_MP2_H
#include <linux/i2c.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#define PCI_DEVICE_ID_AMD_MP2 0x15E6
struct amd_i2c_common;
struct amd_mp2_dev;
enum {
/* MP2 C2P Message Registers */
AMD_C2P_MSG0 = 0x10500, /* MP2 Message for I2C0 */
AMD_C2P_MSG1 = 0x10504, /* MP2 Message for I2C1 */
AMD_C2P_MSG2 = 0x10508, /* DRAM Address Lo / Data 0 */
AMD_C2P_MSG3 = 0x1050c, /* DRAM Address HI / Data 1 */
AMD_C2P_MSG4 = 0x10510, /* Data 2 */
AMD_C2P_MSG5 = 0x10514, /* Data 3 */
AMD_C2P_MSG6 = 0x10518, /* Data 4 */
AMD_C2P_MSG7 = 0x1051c, /* Data 5 */
AMD_C2P_MSG8 = 0x10520, /* Data 6 */
AMD_C2P_MSG9 = 0x10524, /* Data 7 */
/* MP2 P2C Message Registers */
AMD_P2C_MSG0 = 0x10680, /* Do not use */
AMD_P2C_MSG1 = 0x10684, /* I2C0 interrupt register */
AMD_P2C_MSG2 = 0x10688, /* I2C1 interrupt register */
AMD_P2C_MSG3 = 0x1068C, /* MP2 debug info */
AMD_P2C_MSG_INTEN = 0x10690, /* MP2 interrupt gen register */
AMD_P2C_MSG_INTSTS = 0x10694, /* Interrupt status */
};
/* Command register data structures */
#define i2c_none (-1)
enum i2c_cmd {
i2c_read = 0,
i2c_write,
i2c_enable,
i2c_disable,
number_of_sensor_discovered,
is_mp2_active,
invalid_cmd = 0xF,
};
enum speed_enum {
speed100k = 0,
speed400k = 1,
speed1000k = 2,
speed1400k = 3,
speed3400k = 4
};
enum mem_type {
use_dram = 0,
use_c2pmsg = 1,
};
/**
* union i2c_cmd_base : bit access of C2P commands
* @i2c_cmd: bit 0..3 i2c R/W command
* @bus_id: bit 4..7 i2c bus index
* @slave_addr: bit 8..15 slave address
* @length: bit 16..27 read/write length
* @i2c_speed: bit 28..30 bus speed
* @mem_type: bit 31 0-DRAM; 1-C2P msg o/p
*/
union i2c_cmd_base {
u32 ul;
struct {
enum i2c_cmd i2c_cmd : 4;
u8 bus_id : 4;
u32 slave_addr : 8;
u32 length : 12;
enum speed_enum i2c_speed : 3;
enum mem_type mem_type : 1;
} s;
};
enum response_type {
invalid_response = 0,
command_success = 1,
command_failed = 2,
};
enum status_type {
i2c_readcomplete_event = 0,
i2c_readfail_event = 1,
i2c_writecomplete_event = 2,
i2c_writefail_event = 3,
i2c_busenable_complete = 4,
i2c_busenable_failed = 5,
i2c_busdisable_complete = 6,
i2c_busdisable_failed = 7,
invalid_data_length = 8,
invalid_slave_address = 9,
invalid_i2cbus_id = 10,
invalid_dram_addr = 11,
invalid_command = 12,
mp2_active = 13,
numberof_sensors_discovered_resp = 14,
i2c_bus_notinitialized
};
/**
* union i2c_event : bit access of P2C events
* @response: bit 0..1 i2c response type
* @status: bit 2..6 status_type
* @mem_type: bit 7 0-DRAM; 1-C2P msg o/p
* @bus_id: bit 8..11 i2c bus id
* @length: bit 12..23 message length
* @slave_addr: bit 24-31 slave address
*/
union i2c_event {
u32 ul;
struct {
enum response_type response : 2;
enum status_type status : 5;
enum mem_type mem_type : 1;
u8 bus_id : 4;
u32 length : 12;
u32 slave_addr : 8;
} r;
};
/**
* struct amd_i2c_common - per bus/i2c adapter context, shared
* between the pci and the platform driver
* @eventval: MP2 event value set by the IRQ handler
* @mp2_dev: MP2 pci device this adapter is part of
* @msg: i2c message
* @cmd_completion: function called by the IRQ handler to signal
* the platform driver
* @reqcmd: requested i2c command type
* @cmd_success: set to true if the MP2 responded to a command with
* the expected status and response type
* @bus_id: bus index
* @i2c_speed: i2c bus speed determined by the slowest slave
* @dma_buf: if msg length > 32, holds the DMA buffer virtual address
* @dma_addr: if msg length > 32, holds the DMA buffer address
*/
struct amd_i2c_common {
union i2c_event eventval;
struct amd_mp2_dev *mp2_dev;
struct i2c_msg *msg;
void (*cmd_completion)(struct amd_i2c_common *i2c_common);
enum i2c_cmd reqcmd;
u8 cmd_success;
u8 bus_id;
enum speed_enum i2c_speed;
u8 *dma_buf;
dma_addr_t dma_addr;
#ifdef CONFIG_PM
int (*suspend)(struct amd_i2c_common *i2c_common);
int (*resume)(struct amd_i2c_common *i2c_common);
#endif /* CONFIG_PM */
};
/**
* struct amd_mp2_dev - per PCI device context
* @pci_dev: PCI driver node
* @busses: MP2 devices may have up to two busses,
* each bus corresponding to an i2c adapter
* @mmio: iommapped registers
* @c2p_lock: controls access to the C2P mailbox shared between
* the two adapters
* @c2p_lock_busid: id of the adapter which locked c2p_lock
*/
struct amd_mp2_dev {
struct pci_dev *pci_dev;
struct amd_i2c_common *busses[2];
void __iomem *mmio;
struct mutex c2p_lock;
u8 c2p_lock_busid;
unsigned int probed;
};
#define ndev_pdev(ndev) ((ndev)->pci_dev)
#define ndev_name(ndev) pci_name(ndev_pdev(ndev))
#define ndev_dev(ndev) (&ndev_pdev(ndev)->dev)
#define work_amd_i2c_common(__work) \
container_of(__work, struct amd_i2c_common, work.work)
/* PCIe communication driver */
int amd_mp2_rw(struct amd_i2c_common *i2c_common, enum i2c_cmd reqcmd);
int amd_mp2_bus_enable_set(struct amd_i2c_common *i2c_common, bool enable);
void amd_mp2_process_event(struct amd_i2c_common *i2c_common);
void amd_mp2_rw_timeout(struct amd_i2c_common *i2c_common);
int amd_mp2_register_cb(struct amd_i2c_common *i2c_common);
int amd_mp2_unregister_cb(struct amd_i2c_common *i2c_common);
struct amd_mp2_dev *amd_mp2_find_device(void);
static inline void amd_mp2_pm_runtime_get(struct amd_mp2_dev *mp2_dev)
{
pm_runtime_get_sync(&mp2_dev->pci_dev->dev);
}
static inline void amd_mp2_pm_runtime_put(struct amd_mp2_dev *mp2_dev)
{
pm_runtime_mark_last_busy(&mp2_dev->pci_dev->dev);
pm_runtime_put_autosuspend(&mp2_dev->pci_dev->dev);
}
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment