summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch922
1 files changed, 922 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch
new file mode 100644
index 000000000..7eac39e5b
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0106-enable-AST2600-I3C.patch
@@ -0,0 +1,922 @@
+From ec463c332c3b954a5a0ef4ceb253701b8190bbb9 Mon Sep 17 00:00:00 2001
+From: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
+Date: Wed, 6 May 2020 18:11:29 -0700
+Subject: [PATCH] enable AST2600 I3C
+
+This commit ports I3C related changes from Aspeed SDK v00.05.05.
+It also includes Vitor's I3C cdev implementation which isn't
+upstreamed yet so it should be refined later.
+
+Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com>
+Signed-off-by: Vitor Soares <soares@synopsys.com>
+Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
+---
+ arch/arm/boot/dts/aspeed-g6.dtsi | 19 +-
+ drivers/clk/clk-ast2600.c | 30 ++-
+ drivers/i3c/Kconfig | 15 ++
+ drivers/i3c/Makefile | 1 +
+ drivers/i3c/i3cdev.c | 428 ++++++++++++++++++++++++++++++
+ drivers/i3c/internals.h | 2 +
+ drivers/i3c/master.c | 39 ++-
+ drivers/i3c/master/Kconfig | 5 +
+ drivers/i3c/master/Makefile | 1 +
+ drivers/i3c/master/aspeed-i3c-global.c | 89 +++++++
+ include/dt-bindings/clock/ast2600-clock.h | 3 +-
+ include/uapi/linux/i3c/i3cdev.h | 38 +++
+ 12 files changed, 646 insertions(+), 24 deletions(-)
+ create mode 100644 drivers/i3c/i3cdev.c
+ create mode 100644 drivers/i3c/master/aspeed-i3c-global.c
+ create mode 100644 include/uapi/linux/i3c/i3cdev.h
+
+diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi
+index 91f431e419d9..33e1b0ef24f0 100644
+--- a/arch/arm/boot/dts/aspeed-g6.dtsi
++++ b/arch/arm/boot/dts/aspeed-g6.dtsi
+@@ -1091,13 +1091,20 @@
+ };
+
+ &i3c {
++ i3cglobal: i3cg@0 {
++ reg = <0x0 0x1000>;
++ compatible = "aspeed,ast2600-i3c-global";
++ resets = <&syscon ASPEED_RESET_I3C_DMA>;
++ status = "disabled";
++ };
++
+ i3c0: i3c0@2000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #interrupt-cells = <1>;
+ reg = <0x2000 0x1000>;
+ compatible = "snps,dw-i3c-master-1.00a";
+- clocks = <&syscon ASPEED_CLK_APB2>;
++ clocks = <&syscon ASPEED_CLK_GATE_I3C0CLK>;
+ resets = <&syscon ASPEED_RESET_I3C0>;
+ bus-frequency = <100000>;
+ interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+@@ -1110,7 +1117,7 @@
+ #interrupt-cells = <1>;
+ reg = <0x3000 0x1000>;
+ compatible = "snps,dw-i3c-master-1.00a";
+- clocks = <&syscon ASPEED_CLK_APB2>;
++ clocks = <&syscon ASPEED_CLK_GATE_I3C1CLK>;
+ resets = <&syscon ASPEED_RESET_I3C1>;
+ bus-frequency = <100000>;
+ interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+@@ -1123,7 +1130,7 @@
+ #interrupt-cells = <1>;
+ reg = <0x4000 0x1000>;
+ compatible = "snps,dw-i3c-master-1.00a";
+- clocks = <&syscon ASPEED_CLK_APB2>;
++ clocks = <&syscon ASPEED_CLK_GATE_I3C2CLK>;
+ resets = <&syscon ASPEED_RESET_I3C2>;
+ bus-frequency = <100000>;
+ interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>;
+@@ -1138,7 +1145,7 @@
+ #interrupt-cells = <1>;
+ reg = <0x5000 0x1000>;
+ compatible = "snps,dw-i3c-master-1.00a";
+- clocks = <&syscon ASPEED_CLK_APB2>;
++ clocks = <&syscon ASPEED_CLK_GATE_I3C3CLK>;
+ resets = <&syscon ASPEED_RESET_I3C3>;
+ bus-frequency = <100000>;
+ interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>;
+@@ -1153,7 +1160,7 @@
+ #interrupt-cells = <1>;
+ reg = <0x6000 0x1000>;
+ compatible = "snps,dw-i3c-master-1.00a";
+- clocks = <&syscon ASPEED_CLK_APB2>;
++ clocks = <&syscon ASPEED_CLK_GATE_I3C4CLK>;
+ resets = <&syscon ASPEED_RESET_I3C4>;
+ bus-frequency = <100000>;
+ interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
+@@ -1168,7 +1175,7 @@
+ #interrupt-cells = <1>;
+ reg = <0x7000 0x1000>;
+ compatible = "snps,dw-i3c-master-1.00a";
+- clocks = <&syscon ASPEED_CLK_APB2>;
++ clocks = <&syscon ASPEED_CLK_GATE_I3C5CLK>;
+ resets = <&syscon ASPEED_RESET_I3C5>;
+ bus-frequency = <100000>;
+ interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>;
+diff --git a/drivers/clk/clk-ast2600.c b/drivers/clk/clk-ast2600.c
+index e07326544fdc..a30dcbb1a3fd 100644
+--- a/drivers/clk/clk-ast2600.c
++++ b/drivers/clk/clk-ast2600.c
+@@ -112,14 +112,14 @@ static const struct aspeed_gate_data aspeed_g6_gates[] = {
+ [ASPEED_CLK_GATE_LHCCLK] = { 37, -1, "lhclk-gate", "lhclk", 0 }, /* LPC master/LPC+ */
+ /* Reserved 38 RSA: no longer used */
+ /* Reserved 39 */
+- [ASPEED_CLK_GATE_I3C0CLK] = { 40, 40, "i3c0clk-gate", NULL, 0 }, /* I3C0 */
+- [ASPEED_CLK_GATE_I3C1CLK] = { 41, 41, "i3c1clk-gate", NULL, 0 }, /* I3C1 */
+- [ASPEED_CLK_GATE_I3C2CLK] = { 42, 42, "i3c2clk-gate", NULL, 0 }, /* I3C2 */
+- [ASPEED_CLK_GATE_I3C3CLK] = { 43, 43, "i3c3clk-gate", NULL, 0 }, /* I3C3 */
+- [ASPEED_CLK_GATE_I3C4CLK] = { 44, 44, "i3c4clk-gate", NULL, 0 }, /* I3C4 */
+- [ASPEED_CLK_GATE_I3C5CLK] = { 45, 45, "i3c5clk-gate", NULL, 0 }, /* I3C5 */
+- [ASPEED_CLK_GATE_I3C6CLK] = { 46, 46, "i3c6clk-gate", NULL, 0 }, /* I3C6 */
+- [ASPEED_CLK_GATE_I3C7CLK] = { 47, 47, "i3c7clk-gate", NULL, 0 }, /* I3C7 */
++ [ASPEED_CLK_GATE_I3C0CLK] = { 40, 40, "i3c0clk-gate", "i3cclk", 0 }, /* I3C0 */
++ [ASPEED_CLK_GATE_I3C1CLK] = { 41, 41, "i3c1clk-gate", "i3cclk", 0 }, /* I3C1 */
++ [ASPEED_CLK_GATE_I3C2CLK] = { 42, 42, "i3c2clk-gate", "i3cclk", 0 }, /* I3C2 */
++ [ASPEED_CLK_GATE_I3C3CLK] = { 43, 43, "i3c3clk-gate", "i3cclk", 0 }, /* I3C3 */
++ [ASPEED_CLK_GATE_I3C4CLK] = { 44, 44, "i3c4clk-gate", "i3cclk", 0 }, /* I3C4 */
++ [ASPEED_CLK_GATE_I3C5CLK] = { 45, 45, "i3c5clk-gate", "i3cclk", 0 }, /* I3C5 */
++ [ASPEED_CLK_GATE_I3C6CLK] = { 46, 46, "i3c6clk-gate", "i3cclk", 0 }, /* I3C6 */
++ [ASPEED_CLK_GATE_I3C7CLK] = { 47, 47, "i3c7clk-gate", "i3cclk", 0 }, /* I3C7 */
+ [ASPEED_CLK_GATE_UART1CLK] = { 48, -1, "uart1clk-gate", "uart", 0 }, /* UART1 */
+ [ASPEED_CLK_GATE_UART2CLK] = { 49, -1, "uart2clk-gate", "uart", 0 }, /* UART2 */
+ [ASPEED_CLK_GATE_UART3CLK] = { 50, -1, "uart3clk-gate", "uart", 0 }, /* UART3 */
+@@ -749,6 +749,20 @@ static void __init aspeed_g6_cc(struct regmap *map)
+ /* USB 2.0 port1 phy 40MHz clock */
+ hw = clk_hw_register_fixed_rate(NULL, "usb-phy-40m", NULL, 0, 40000000);
+ aspeed_g6_clk_data->hws[ASPEED_CLK_USBPHY_40M] = hw;
++
++ /* i3c clock source */
++ regmap_read(map, ASPEED_G6_CLK_SELECTION5, &val);
++ if(val & BIT(31)) {
++ val = (val >> 28) & 0x7;
++ if(val)
++ div = val + 1;
++ else
++ div = val + 2;
++ hw = clk_hw_register_fixed_factor(NULL, "i3cclk", "apll", 0, 1, div);
++ } else {
++ hw = clk_hw_register_fixed_factor(NULL, "i3cclk", "ahb", 0, 1, 1);
++ }
++ aspeed_g6_clk_data->hws[ASPEED_CLK_I3C] = hw;
+ };
+
+ static void __init aspeed_g6_cc_init(struct device_node *np)
+diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
+index 30a441506f61..01642768ab5f 100644
+--- a/drivers/i3c/Kconfig
++++ b/drivers/i3c/Kconfig
+@@ -20,5 +20,20 @@ menuconfig I3C
+ will be called i3c.
+
+ if I3C
++
++config I3CDEV
++ tristate "I3C device interface"
++ depends on I3C
++ help
++ Say Y here to use i3c-* device files, usually found in the /dev
++ directory on your system. They make it possible to have user-space
++ programs use the I3C devices.
++
++ This support is also available as a module. If so, the module
++ will be called i3cdev.
++
++ Note that this application programming interface is EXPERIMENTAL
++ and hence SUBJECT TO CHANGE WITHOUT NOTICE while it stabilizes.
++
+ source "drivers/i3c/master/Kconfig"
+ endif # I3C
+diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
+index 11982efbc6d9..606d422841b2 100644
+--- a/drivers/i3c/Makefile
++++ b/drivers/i3c/Makefile
+@@ -1,4 +1,5 @@
+ # SPDX-License-Identifier: GPL-2.0
+ i3c-y := device.o master.o
+ obj-$(CONFIG_I3C) += i3c.o
++obj-$(CONFIG_I3CDEV) += i3cdev.o
+ obj-$(CONFIG_I3C) += master/
+diff --git a/drivers/i3c/i3cdev.c b/drivers/i3c/i3cdev.c
+new file mode 100644
+index 000000000000..07f5641a902d
+--- /dev/null
++++ b/drivers/i3c/i3cdev.c
+@@ -0,0 +1,428 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
++ *
++ * Author: Vitor Soares <soares@synopsys.com>
++ */
++
++#include <linux/cdev.h>
++#include <linux/compat.h>
++#include <linux/device.h>
++#include <linux/fs.h>
++#include <linux/init.h>
++#include <linux/jiffies.h>
++#include <linux/kernel.h>
++#include <linux/list.h>
++#include <linux/module.h>
++#include <linux/notifier.h>
++#include <linux/slab.h>
++#include <linux/uaccess.h>
++
++#include <linux/i3c/i3cdev.h>
++
++#include "internals.h"
++
++struct i3cdev_data {
++ struct list_head list;
++ struct i3c_device *i3c;
++ struct cdev cdev;
++ struct device *dev;
++ int id;
++};
++
++static DEFINE_IDA(i3cdev_ida);
++static dev_t i3cdev_number;
++#define I3C_MINORS 16 /* 16 I3C devices supported for now */
++
++static LIST_HEAD(i3cdev_list);
++static DEFINE_SPINLOCK(i3cdev_list_lock);
++
++static struct i3cdev_data *i3cdev_get_by_i3c(struct i3c_device *i3c)
++{
++ struct i3cdev_data *i3cdev;
++
++ spin_lock(&i3cdev_list_lock);
++ list_for_each_entry(i3cdev, &i3cdev_list, list) {
++ if (i3cdev->i3c == i3c)
++ goto found;
++ }
++
++ i3cdev = NULL;
++
++found:
++ spin_unlock(&i3cdev_list_lock);
++ return i3cdev;
++}
++
++static struct i3cdev_data *get_free_i3cdev(struct i3c_device *i3c)
++{
++ struct i3cdev_data *i3cdev;
++ int id;
++
++ id = ida_simple_get(&i3cdev_ida, 0, I3C_MINORS, GFP_KERNEL);
++ if (id < 0) {
++ pr_err("i3cdev: no minor number available!\n");
++ return ERR_PTR(id);
++ }
++
++ i3cdev = kzalloc(sizeof(*i3cdev), GFP_KERNEL);
++ if (!i3cdev) {
++ ida_simple_remove(&i3cdev_ida, id);
++ return ERR_PTR(-ENOMEM);
++ }
++
++ i3cdev->i3c = i3c;
++ i3cdev->id = id;
++
++ spin_lock(&i3cdev_list_lock);
++ list_add_tail(&i3cdev->list, &i3cdev_list);
++ spin_unlock(&i3cdev_list_lock);
++
++ return i3cdev;
++}
++
++static void put_i3cdev(struct i3cdev_data *i3cdev)
++{
++ spin_lock(&i3cdev_list_lock);
++ list_del(&i3cdev->list);
++ spin_unlock(&i3cdev_list_lock);
++ kfree(i3cdev);
++}
++
++static ssize_t
++i3cdev_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
++{
++ struct i3c_device *i3c = file->private_data;
++ struct i3c_priv_xfer xfers = {
++ .rnw = true,
++ .len = count,
++ };
++ char *tmp;
++ int ret;
++
++ tmp = kzalloc(count, GFP_KERNEL);
++ if (!tmp)
++ return -ENOMEM;
++
++ xfers.data.in = tmp;
++
++ dev_dbg(&i3c->dev, "Reading %zu bytes.\n", count);
++
++ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
++ if (!ret)
++ ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
++
++ kfree(tmp);
++ return ret;
++}
++
++static ssize_t
++i3cdev_write(struct file *file, const char __user *buf, size_t count,
++ loff_t *f_pos)
++{
++ struct i3c_device *i3c = file->private_data;
++ struct i3c_priv_xfer xfers = {
++ .rnw = false,
++ .len = count,
++ };
++ char *tmp;
++ int ret;
++
++ tmp = memdup_user(buf, count);
++ if (IS_ERR(tmp))
++ return PTR_ERR(tmp);
++
++ xfers.data.out = tmp;
++
++ dev_dbg(&i3c->dev, "Writing %zu bytes.\n", count);
++
++ ret = i3c_device_do_priv_xfers(i3c, &xfers, 1);
++ kfree(tmp);
++ return (!ret) ? count : ret;
++}
++
++static int
++i3cdev_do_priv_xfer(struct i3c_device *dev, struct i3c_ioc_priv_xfer *xfers,
++ unsigned int nxfers)
++{
++ struct i3c_priv_xfer *k_xfers;
++ u8 **data_ptrs;
++ int i, ret = 0;
++
++ k_xfers = kcalloc(nxfers, sizeof(*k_xfers), GFP_KERNEL);
++ if (!k_xfers)
++ return -ENOMEM;
++
++ data_ptrs = kcalloc(nxfers, sizeof(*data_ptrs), GFP_KERNEL);
++ if (!data_ptrs) {
++ ret = -ENOMEM;
++ goto err_free_k_xfer;
++ }
++
++ for (i = 0; i < nxfers; i++) {
++ data_ptrs[i] = memdup_user((const u8 __user *)
++ (uintptr_t)xfers[i].data,
++ xfers[i].len);
++ if (IS_ERR(data_ptrs[i])) {
++ ret = PTR_ERR(data_ptrs[i]);
++ break;
++ }
++
++ k_xfers[i].len = xfers[i].len;
++ if (xfers[i].rnw) {
++ k_xfers[i].rnw = true;
++ k_xfers[i].data.in = data_ptrs[i];
++ } else {
++ k_xfers[i].rnw = false;
++ k_xfers[i].data.out = data_ptrs[i];
++ }
++ }
++
++ if (ret < 0) {
++ i--;
++ goto err_free_mem;
++ }
++
++ ret = i3c_device_do_priv_xfers(dev, k_xfers, nxfers);
++ if (ret)
++ goto err_free_mem;
++
++ for (i = 0; i < nxfers; i++) {
++ if (xfers[i].rnw) {
++ if (copy_to_user((void __user *)(uintptr_t)xfers[i].data,
++ data_ptrs[i], xfers[i].len))
++ ret = -EFAULT;
++ }
++ }
++
++err_free_mem:
++ for (; i >= 0; i--)
++ kfree(data_ptrs[i]);
++ kfree(data_ptrs);
++err_free_k_xfer:
++ kfree(k_xfers);
++ return ret;
++}
++
++static struct i3c_ioc_priv_xfer *
++i3cdev_get_ioc_priv_xfer(unsigned int cmd, struct i3c_ioc_priv_xfer *u_xfers,
++ unsigned int *nxfers)
++{
++ u32 tmp = _IOC_SIZE(cmd);
++
++ if ((tmp % sizeof(struct i3c_ioc_priv_xfer)) != 0)
++ return ERR_PTR(-EINVAL);
++
++ *nxfers = tmp / sizeof(struct i3c_ioc_priv_xfer);
++ if (*nxfers == 0)
++ return NULL;
++
++ return memdup_user(u_xfers, tmp);
++}
++
++static int
++i3cdev_ioc_priv_xfer(struct i3c_device *i3c, unsigned int cmd,
++ struct i3c_ioc_priv_xfer *u_xfers)
++{
++ struct i3c_ioc_priv_xfer *k_xfers;
++ unsigned int nxfers;
++ int ret;
++
++ k_xfers = i3cdev_get_ioc_priv_xfer(cmd, u_xfers, &nxfers);
++ if (IS_ERR_OR_NULL(k_xfers))
++ return PTR_ERR(k_xfers);
++
++ ret = i3cdev_do_priv_xfer(i3c, k_xfers, nxfers);
++
++ kfree(k_xfers);
++
++ return ret;
++}
++
++static long
++i3cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
++{
++ struct i3c_device *i3c = file->private_data;
++
++ dev_dbg(&i3c->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n", cmd, arg);
++
++ if (_IOC_TYPE(cmd) != I3C_DEV_IOC_MAGIC)
++ return -ENOTTY;
++
++ /* Check command number and direction */
++ if (_IOC_NR(cmd) == _IOC_NR(I3C_IOC_PRIV_XFER(0)) &&
++ _IOC_DIR(cmd) == (_IOC_READ | _IOC_WRITE))
++ return i3cdev_ioc_priv_xfer(i3c, cmd,
++ (struct i3c_ioc_priv_xfer __user *)arg);
++
++ return 0;
++}
++
++static int i3cdev_open(struct inode *inode, struct file *file)
++{
++ struct i3cdev_data *i3cdev = container_of(inode->i_cdev,
++ struct i3cdev_data,
++ cdev);
++
++ file->private_data = i3cdev->i3c;
++
++ return 0;
++}
++
++static int i3cdev_release(struct inode *inode, struct file *file)
++{
++ file->private_data = NULL;
++
++ return 0;
++}
++
++static const struct file_operations i3cdev_fops = {
++ .owner = THIS_MODULE,
++ .read = i3cdev_read,
++ .write = i3cdev_write,
++ .unlocked_ioctl = i3cdev_ioctl,
++ .open = i3cdev_open,
++ .release = i3cdev_release,
++};
++
++/* ------------------------------------------------------------------------- */
++
++static struct class *i3cdev_class;
++
++static int i3cdev_attach(struct device *dev, void *dummy)
++{
++ struct i3cdev_data *i3cdev;
++ struct i3c_device *i3c;
++ int res;
++
++ if (dev->type == &i3c_masterdev_type || dev->driver)
++ return 0;
++
++ i3c = dev_to_i3cdev(dev);
++
++ /* Get a device */
++ i3cdev = get_free_i3cdev(i3c);
++ if (IS_ERR(i3cdev))
++ return PTR_ERR(i3cdev);
++
++ cdev_init(&i3cdev->cdev, &i3cdev_fops);
++ i3cdev->cdev.owner = THIS_MODULE;
++ res = cdev_add(&i3cdev->cdev,
++ MKDEV(MAJOR(i3cdev_number), i3cdev->id), 1);
++ if (res)
++ goto error_cdev;
++
++ /* register this i3c device with the driver core */
++ i3cdev->dev = device_create(i3cdev_class, &i3c->dev,
++ MKDEV(MAJOR(i3cdev_number), i3cdev->id),
++ NULL, "i3c-%s", dev_name(&i3c->dev));
++ if (IS_ERR(i3cdev->dev)) {
++ res = PTR_ERR(i3cdev->dev);
++ goto error;
++ }
++ pr_debug("i3cdev: I3C device [%s] registered as minor %d\n",
++ dev_name(&i3c->dev), i3cdev->id);
++ return 0;
++
++error:
++ cdev_del(&i3cdev->cdev);
++error_cdev:
++ put_i3cdev(i3cdev);
++ return res;
++}
++
++static int i3cdev_detach(struct device *dev, void *dummy)
++{
++ struct i3cdev_data *i3cdev;
++ struct i3c_device *i3c;
++
++ if (dev->type == &i3c_masterdev_type)
++ return 0;
++
++ i3c = dev_to_i3cdev(dev);
++
++ i3cdev = i3cdev_get_by_i3c(i3c);
++ if (!i3cdev)
++ return 0;
++
++ cdev_del(&i3cdev->cdev);
++ device_destroy(i3cdev_class, MKDEV(MAJOR(i3cdev_number), i3cdev->id));
++ ida_simple_remove(&i3cdev_ida, i3cdev->id);
++ put_i3cdev(i3cdev);
++
++ pr_debug("i3cdev: device [%s] unregistered\n", dev_name(&i3c->dev));
++
++ return 0;
++}
++
++static int i3cdev_notifier_call(struct notifier_block *nb,
++ unsigned long action,
++ void *data)
++{
++ struct device *dev = data;
++
++ switch (action) {
++ case BUS_NOTIFY_ADD_DEVICE:
++ case BUS_NOTIFY_UNBOUND_DRIVER:
++ return i3cdev_attach(dev, NULL);
++ case BUS_NOTIFY_DEL_DEVICE:
++ case BUS_NOTIFY_BOUND_DRIVER:
++ return i3cdev_detach(dev, NULL);
++ }
++
++ return 0;
++}
++
++static struct notifier_block i3c_notifier = {
++ .notifier_call = i3cdev_notifier_call,
++};
++
++static int __init i3cdev_init(void)
++{
++ int res;
++
++ /* Dynamically request unused major number */
++ res = alloc_chrdev_region(&i3cdev_number, 0, I3C_MINORS, "i3c");
++ if (res)
++ goto out;
++
++ /* Create a classe to populate sysfs entries*/
++ i3cdev_class = class_create(THIS_MODULE, "i3cdev");
++ if (IS_ERR(i3cdev_class)) {
++ res = PTR_ERR(i3cdev_class);
++ goto out_unreg_chrdev;
++ }
++
++ /* Keep track of busses which have devices to add or remove later */
++ res = bus_register_notifier(&i3c_bus_type, &i3c_notifier);
++ if (res)
++ goto out_unreg_class;
++
++ /* Bind to already existing device without driver right away */
++ i3c_for_each_dev(NULL, i3cdev_attach);
++
++ return 0;
++
++out_unreg_class:
++ class_destroy(i3cdev_class);
++out_unreg_chrdev:
++ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
++out:
++ pr_err("%s: Driver Initialisation failed\n", __FILE__);
++ return res;
++}
++
++static void __exit i3cdev_exit(void)
++{
++ bus_unregister_notifier(&i3c_bus_type, &i3c_notifier);
++ i3c_for_each_dev(NULL, i3cdev_detach);
++ class_destroy(i3cdev_class);
++ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
++}
++
++MODULE_AUTHOR("Vitor Soares <soares@synopsys.com>");
++MODULE_DESCRIPTION("I3C /dev entries driver");
++MODULE_LICENSE("GPL");
++
++module_init(i3cdev_init);
++module_exit(i3cdev_exit);
+diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
+index 86b7b44cfca2..a6deedf5ce06 100644
+--- a/drivers/i3c/internals.h
++++ b/drivers/i3c/internals.h
+@@ -11,6 +11,7 @@
+ #include <linux/i3c/master.h>
+
+ extern struct bus_type i3c_bus_type;
++extern const struct device_type i3c_masterdev_type;
+
+ void i3c_bus_normaluse_lock(struct i3c_bus *bus);
+ void i3c_bus_normaluse_unlock(struct i3c_bus *bus);
+@@ -23,4 +24,5 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev);
+ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
+ const struct i3c_ibi_setup *req);
+ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev);
++int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *));
+ #endif /* I3C_INTERNAL_H */
+diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
+index 5c051dba32a5..53910fb6702e 100644
+--- a/drivers/i3c/master.c
++++ b/drivers/i3c/master.c
+@@ -321,6 +321,7 @@ struct bus_type i3c_bus_type = {
+ .probe = i3c_device_probe,
+ .remove = i3c_device_remove,
+ };
++EXPORT_SYMBOL_GPL(i3c_bus_type);
+
+ static enum i3c_addr_slot_status
+ i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr)
+@@ -523,11 +524,12 @@ static void i3c_masterdev_release(struct device *dev)
+ of_node_put(dev->of_node);
+ }
+
+-static const struct device_type i3c_masterdev_type = {
++const struct device_type i3c_masterdev_type = {
+ .groups = i3c_masterdev_groups,
+ };
++EXPORT_SYMBOL_GPL(i3c_masterdev_type);
+
+-int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode,
++static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode,
+ unsigned long max_i2c_scl_rate)
+ {
+ struct i3c_master_controller *master = i3c_bus_to_i3c_master(i3cbus);
+@@ -1410,19 +1412,19 @@ static void i3c_master_detach_i2c_dev(struct i2c_dev_desc *dev)
+ master->ops->detach_i2c_dev(dev);
+ }
+
+-static void i3c_master_pre_assign_dyn_addr(struct i3c_dev_desc *dev)
++static int i3c_master_pre_assign_dyn_addr(struct i3c_dev_desc *dev)
+ {
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+ int ret;
+
+ if (!dev->boardinfo || !dev->boardinfo->init_dyn_addr ||
+ !dev->boardinfo->static_addr)
+- return;
++ return -1;
+
+ ret = i3c_master_setdasa_locked(master, dev->info.static_addr,
+ dev->boardinfo->init_dyn_addr);
+ if (ret)
+- return;
++ return ret;
+
+ dev->info.dyn_addr = dev->boardinfo->init_dyn_addr;
+ ret = i3c_master_reattach_i3c_dev(dev, 0);
+@@ -1433,10 +1435,11 @@ static void i3c_master_pre_assign_dyn_addr(struct i3c_dev_desc *dev)
+ if (ret)
+ goto err_rstdaa;
+
+- return;
++ return 0;
+
+ err_rstdaa:
+ i3c_master_rstdaa_locked(master, dev->boardinfo->init_dyn_addr);
++ return ret;
+ }
+
+ static void
+@@ -1631,7 +1634,7 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
+ enum i3c_addr_slot_status status;
+ struct i2c_dev_boardinfo *i2cboardinfo;
+ struct i3c_dev_boardinfo *i3cboardinfo;
+- struct i3c_dev_desc *i3cdev;
++ struct i3c_dev_desc *i3cdev, *i3ctmp;
+ struct i2c_dev_desc *i2cdev;
+ int ret;
+
+@@ -1730,8 +1733,14 @@ static int i3c_master_bus_init(struct i3c_master_controller *master)
+ * Pre-assign dynamic address and retrieve device information if
+ * needed.
+ */
+- i3c_bus_for_each_i3cdev(&master->bus, i3cdev)
+- i3c_master_pre_assign_dyn_addr(i3cdev);
++ list_for_each_entry_safe(i3cdev, i3ctmp, &master->bus.devs.i3c,
++ common.node) {
++ ret = i3c_master_pre_assign_dyn_addr(i3cdev);
++ if (ret) {
++ i3c_master_detach_i3c_dev(i3cdev);
++ i3c_master_free_i3c_dev(i3cdev);
++ }
++ }
+
+ ret = i3c_master_do_daa(master);
+ if (ret)
+@@ -2638,6 +2647,18 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
+ dev->ibi = NULL;
+ }
+
++int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *))
++{
++ int res;
++
++ mutex_lock(&i3c_core_lock);
++ res = bus_for_each_dev(&i3c_bus_type, NULL, data, fn);
++ mutex_unlock(&i3c_core_lock);
++
++ return res;
++}
++EXPORT_SYMBOL_GPL(i3c_for_each_dev);
++
+ static int __init i3c_init(void)
+ {
+ return bus_register(&i3c_bus_type);
+diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
+index 4e80a1fcbf91..693f9aba2b17 100644
+--- a/drivers/i3c/master/Kconfig
++++ b/drivers/i3c/master/Kconfig
+@@ -21,3 +21,8 @@ config DW_I3C_MASTER
+
+ This driver can also be built as a module. If so, the module
+ will be called dw-i3c-master.
++
++config ASPEED_I3C_GLOBAL
++ tristate "ASPEED I3C global driver"
++ depends on I3C
++ depends on MACH_ASPEED_G6
+diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
+index 7eea9e086144..09057d1432cc 100644
+--- a/drivers/i3c/master/Makefile
++++ b/drivers/i3c/master/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ obj-$(CONFIG_CDNS_I3C_MASTER) += i3c-master-cdns.o
+ obj-$(CONFIG_DW_I3C_MASTER) += dw-i3c-master.o
++obj-$(CONFIG_ASPEED_I3C_GLOBAL) += aspeed-i3c-global.o
+diff --git a/drivers/i3c/master/aspeed-i3c-global.c b/drivers/i3c/master/aspeed-i3c-global.c
+new file mode 100644
+index 000000000000..9c3e58794a3e
+--- /dev/null
++++ b/drivers/i3c/master/aspeed-i3c-global.c
+@@ -0,0 +1,89 @@
++/*
++ * Aspeed I2C Interrupt Controller.
++ *
++ * Copyright (C) 2012-2017 ASPEED Technology Inc.
++ * Copyright 2017 IBM Corporation
++ * Copyright 2017 Google, Inc.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#include <linux/clk.h>
++#include <linux/irq.h>
++#include <linux/irqchip.h>
++#include <linux/irqchip/chained_irq.h>
++#include <linux/irqdomain.h>
++#include <linux/module.h>
++#include <linux/of_platform.h>
++#include <linux/platform_device.h>
++#include <linux/of_address.h>
++#include <linux/of_irq.h>
++#include <linux/io.h>
++#include <linux/reset.h>
++#include <linux/delay.h>
++
++#define ASPEED_I3CG_CTRL(x) (0x10 + (x*0x10))
++#define ASPEED_I3CG_SET(x) (0x14 + (x*0x10))
++
++struct aspeed_i3c_global {
++ void __iomem *base;
++ struct reset_control *rst;
++};
++
++static const struct of_device_id aspeed_i3c_of_match[] = {
++ { .compatible = "aspeed,ast2600-i3c-global", },
++ {},
++};
++
++static int aspeed_i3c_global_probe(struct platform_device *pdev)
++{
++ struct aspeed_i3c_global *i3c_global;
++ struct device_node *node = pdev->dev.of_node;
++ int i = 0;
++
++ i3c_global = kzalloc(sizeof(*i3c_global), GFP_KERNEL);
++ if (!i3c_global)
++ return -ENOMEM;
++
++ i3c_global->base = of_iomap(node, 0);
++ if (!i3c_global->base) {
++ return -ENOMEM;
++ }
++
++ i3c_global->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
++
++ if (IS_ERR(i3c_global->rst)) {
++ dev_err(&pdev->dev,
++ "missing or invalid reset controller device tree entry");
++ return PTR_ERR(i3c_global->rst);
++ }
++
++ reset_control_assert(i3c_global->rst);
++ udelay(3);
++ reset_control_deassert(i3c_global->rst);
++
++ /* init */
++ for(i = 0; i < 5; i++)
++ writel(0x000474c4, i3c_global->base + ASPEED_I3CG_SET(i));
++
++ return 0;
++}
++
++static struct platform_driver aspeed_i3c_driver = {
++ .probe = aspeed_i3c_global_probe,
++ .driver = {
++ .name = KBUILD_MODNAME,
++ .of_match_table = aspeed_i3c_of_match,
++ },
++};
++
++static int __init aspeed_i3c_global_init(void)
++{
++ return platform_driver_register(&aspeed_i3c_driver);
++}
++postcore_initcall(aspeed_i3c_global_init);
++
++MODULE_AUTHOR("Ryan Chen");
++MODULE_DESCRIPTION("ASPEED I3C Global Driver");
++MODULE_LICENSE("GPL v2");
+diff --git a/include/dt-bindings/clock/ast2600-clock.h b/include/dt-bindings/clock/ast2600-clock.h
+index 26f84584a821..6cc47373cc97 100644
+--- a/include/dt-bindings/clock/ast2600-clock.h
++++ b/include/dt-bindings/clock/ast2600-clock.h
+@@ -88,7 +88,8 @@
+ #define ASPEED_CLK_MAC3RCLK 69
+ #define ASPEED_CLK_MAC4RCLK 70
+ #define ASPEED_CLK_UART5 71
+-#define ASPEED_CLK_MAX 72
++#define ASPEED_CLK_I3C 72
++#define ASPEED_CLK_MAX 73
+
+ /* Only list resets here that are not part of a gate */
+ #define ASPEED_RESET_ADC 55
+diff --git a/include/uapi/linux/i3c/i3cdev.h b/include/uapi/linux/i3c/i3cdev.h
+new file mode 100644
+index 000000000000..0897313f5516
+--- /dev/null
++++ b/include/uapi/linux/i3c/i3cdev.h
+@@ -0,0 +1,38 @@
++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
++/*
++ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
++ *
++ * Author: Vitor Soares <vitor.soares@synopsys.com>
++ */
++
++#ifndef _UAPI_I3C_DEV_H_
++#define _UAPI_I3C_DEV_H_
++
++#include <linux/types.h>
++#include <linux/ioctl.h>
++
++/* IOCTL commands */
++#define I3C_DEV_IOC_MAGIC 0x07
++
++/**
++ * struct i3c_ioc_priv_xfer - I3C SDR ioctl private transfer
++ * @data: Holds pointer to userspace buffer with transmit data.
++ * @len: Length of data buffer buffers, in bytes.
++ * @rnw: encodes the transfer direction. true for a read, false for a write
++ */
++struct i3c_ioc_priv_xfer {
++ __u64 data;
++ __u16 len;
++ __u8 rnw;
++ __u8 pad[5];
++};
++
++
++#define I3C_PRIV_XFER_SIZE(N) \
++ ((((sizeof(struct i3c_ioc_priv_xfer)) * (N)) < (1 << _IOC_SIZEBITS)) \
++ ? ((sizeof(struct i3c_ioc_priv_xfer)) * (N)) : 0)
++
++#define I3C_IOC_PRIV_XFER(N) \
++ _IOC(_IOC_READ|_IOC_WRITE, I3C_DEV_IOC_MAGIC, 30, I3C_PRIV_XFER_SIZE(N))
++
++#endif
+--
+2.7.4
+