From 155c1c4f0f81c4c8a5469a31146ea1167730cb59 Mon Sep 17 00:00:00 2001 From: Alistair Popple Date: Tue, 29 Sep 2015 14:50:15 +1000 Subject: [PATCH 1/2] gpio: Move aspeed Kconfig entry to correct menu The Aspeed GPIO menu entry was added under "PCI GPIO expanders" which depends on PCI which may not be enabled for Aspeed. Move the entry to the "Memory mapped GPIO drivers" which describes the Aspeed GPIO driver more correctly. Signed-off-by: Alistair Popple --- drivers/gpio/Kconfig | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c5621cbc22957c..e39dabf3c5fcfa 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -120,6 +120,13 @@ config GPIO_ALTERA If driver is built as a module it will be called gpio-altera. +config GPIO_ASPEED + bool "Aspeed AST2400 GPIO support" + depends on ARCH_ASPEED && OF + select GENERIC_IRQ_CHIP + help + Say Y here to support Aspeed AST2400 GPIO. + config GPIO_BCM_KONA bool "Broadcom Kona GPIO" depends on OF_GPIO && (ARCH_BCM_MOBILE || COMPILE_TEST) @@ -968,14 +975,6 @@ config GPIO_SODAVILLE select GENERIC_IRQ_CHIP help Say Y here to support Intel Sodaville GPIO. - -config GPIO_ASPEED - bool "Aspeed AST2400 GPIO support" - depends on ARCH_ASPEED && OF - select GENERIC_IRQ_CHIP - help - Say Y here to support Aspeed AST2400 GPIO. - endmenu menu "SPI GPIO expanders" From 446301ebd4636451f48ad25741809818288bdf0c Mon Sep 17 00:00:00 2001 From: Alistair Popple Date: Tue, 29 Sep 2015 15:17:54 +1000 Subject: [PATCH 2/2] bt/ipmi: Add Aspeed BT IPMI host driver This patch adds a simple device driver to expose the iBT interface on Aspeed chips as a character device (/dev/bt). Signed-off-by: Alistair Popple --- arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts | 5 + drivers/misc/Kconfig | 5 + drivers/misc/Makefile | 1 + drivers/misc/bt-host.c | 385 ++++++++++++++++++ 4 files changed, 396 insertions(+) create mode 100644 drivers/misc/bt-host.c diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts index ff2ce3b93d24dd..2fb6082214cee8 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts @@ -85,6 +85,11 @@ reg = <0x1e72000 0x8000>; // 32K }; + ibt@1e789150 { + compatible = "bt-host"; + reg = <0x1e789140 0x18>; + }; + i2c0: i2c@1e78a040 { compatible = "aspeed,ast2400-i2c", "i2c"; reg = <0x1e78a040 0x40>, <0x1e78a000 0x40>; diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 42c38525904b85..a5553af7ed0f20 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -515,6 +515,11 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. +config ASPEED_BT_IPMI_HOST + tristate "BT IPMI host driver" + help + Support for the Aspeed BT ipmi host. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d056fb7186fe1b..eca67445ed2174 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -55,3 +55,4 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_ASPEED_BT_IPMI_HOST) += bt-host.o diff --git a/drivers/misc/bt-host.c b/drivers/misc/bt-host.c new file mode 100644 index 00000000000000..1f6e1c49b4af17 --- /dev/null +++ b/drivers/misc/bt-host.c @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "bt" +#define BT_NUM_DEVS 1 + +#define BT_IO_BASE 0xe4 +#define BT_IRQ 10 + +#define BT_CR0 0x0 +#define BT_CR0_IO_BASE 16 +#define BT_CR0_IRQ 12 +#define BT_CR0_EN_CLR_SLV_RDP 0x8 +#define BT_CR0_EN_CLR_SLV_WRP 0x4 +#define BT_CR0_ENABLE_IBT 0x1 +#define BT_CR1 0x4 +#define BT_CR2 0x8 +#define BT_CR3 0xc +#define BT_CTRL 0x10 +#define BT_CTRL_B_BUSY 0x80 +#define BT_CTRL_H_BUSY 0x40 +#define BT_CTRL_OEM0 0x20 +#define BT_CTRL_SMS_ATN 0x10 +#define BT_CTRL_B2H_ATN 0x08 +#define BT_CTRL_H2B_ATN 0x04 +#define BT_CTRL_CLR_RD_PTR 0x02 +#define BT_CTRL_CLR_WR_PTR 0x01 +#define BT_BMC2HOST 0x14 +#define BT_INTMASK 0x18 +#define BT_INTMASK_B2H_IRQEN 0x01 +#define BT_INTMASK_B2H_IRQ 0x02 +#define BT_INTMASK_BMC_HWRST 0x80 + +dev_t bt_host_devt; + +struct bt_host { + struct device dev; + struct cdev cdev; + void *base; + int open_count; + wait_queue_head_t queue; + unsigned int ctrl; + struct timer_list poll_timer; +}; + +static struct bt_host *bt_host; + +static char bt_inb(struct bt_host *bt_host, int reg) +{ + return ioread8(bt_host->base + reg); +} + +static void bt_outb(struct bt_host *bt_host, char data, int reg) +{ + iowrite8(data, bt_host->base + reg); +} + +static void clr_rd_ptr(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_CLR_RD_PTR, BT_CTRL); +} + +static void clr_wr_ptr(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_CLR_WR_PTR, BT_CTRL); +} + +static int h2b_atn(struct bt_host *bt_host) +{ + return !!(bt_inb(bt_host, BT_CTRL) & BT_CTRL_H2B_ATN); +} + +static void clr_h2b_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_H2B_ATN, BT_CTRL); +} + +static void set_b_busy(struct bt_host *bt_host) +{ + if (!(bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY)) + bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL);} + +static void clr_b_busy(struct bt_host *bt_host) +{ + if (bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY) + bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL); +} + +static void set_b2h_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_B2H_ATN, BT_CTRL); +} + +static int b2h_atn(struct bt_host *bt_host) +{ + return !!(bt_inb(bt_host, BT_CTRL) & BT_CTRL_B2H_ATN); +} + +static int h_busy(struct bt_host *bt_host) +{ + return !!(bt_inb(bt_host, BT_CTRL) & BT_CTRL_H_BUSY); +} + +static char bt_read(struct bt_host *bt_host) +{ + char result = bt_inb(bt_host, BT_BMC2HOST); + + return result; +} + +static void bt_write(struct bt_host *bt_host, char c) +{ + bt_outb(bt_host, c, BT_BMC2HOST); +} + +static int bt_host_open(struct inode *inode, struct file *file) +{ + file->private_data = bt_host; + + clr_b_busy(bt_host); + + return 0; +} + +static ssize_t bt_host_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + char __user *p = buf; + char len; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + WARN_ON(*ppos); + + if (wait_event_interruptible(bt_host->queue, + bt_inb(bt_host, BT_CTRL) + & BT_CTRL_H2B_ATN)) + return -ERESTARTSYS; + + set_b_busy(bt_host); + clr_h2b_atn(bt_host); + clr_rd_ptr(bt_host); + + len = bt_read(bt_host); + __put_user(len, p++); + + /* We pass the length back as well */ + if (len + 1 > count) + len = count - 1; + + while(len) { + if (__put_user(bt_read(bt_host), p)) + return -EFAULT; + len--; p++; + } + + clr_b_busy(bt_host); + + return p - buf; +} + +static ssize_t bt_host_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + const char __user *p = buf; + char c; + + if (!access_ok(VERIFY_READ, buf, count)) + return -EFAULT; + + WARN_ON(*ppos); + + /* There's no interrupt for clearing host busy so we have to + * poll */ + if (wait_event_interruptible(bt_host->queue, + !(bt_inb(bt_host, BT_CTRL) & + (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN)))) + return -ERESTARTSYS; + + clr_wr_ptr(bt_host); + + while (count) { + if (__get_user(c, p)) + return -EFAULT; + + bt_write(bt_host, c); + count--; p++; + } + + set_b2h_atn(bt_host); + + return p - buf; +} + +static int bt_host_release(struct inode *inode, struct file *file) +{ + set_b_busy(bt_host); + + return 0; +} + +static unsigned int bt_host_poll(struct file *file, poll_table *wait) +{ + uint8_t ctrl = bt_inb(bt_host, BT_CTRL); + unsigned int mask = 0; + + poll_wait(file, &bt_host->queue, wait); + + if (ctrl & BT_CTRL_H2B_ATN) + mask |= POLLIN; + + if (!(ctrl & (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))) + mask |= POLLOUT; + + return mask; +} + +static const struct file_operations bt_host_fops = { + .owner = THIS_MODULE, + .open = bt_host_open, + .read = bt_host_read, + .write = bt_host_write, + .release = bt_host_release, + .poll = bt_host_poll, +}; + +static struct miscdevice bt_host_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "bt", + .fops = &bt_host_fops, +}; + +static void poll_timer(unsigned long data) +{ + bt_host->ctrl = bt_inb(bt_host, BT_CTRL); + bt_host->poll_timer.expires += msecs_to_jiffies(500); + wake_up(&bt_host->queue); + add_timer(&bt_host->poll_timer); +} + +static int bt_host_probe(struct platform_device *pdev) +{ + struct device *dev; + struct resource *res; + int rc, devno = MKDEV(MAJOR(bt_host_devt), 0); + + if (!pdev || !pdev->dev.of_node) + return -ENODEV; + + dev = &pdev->dev; + dev_info(dev, "Found bt host device\n"); + + if (bt_host) { + dev_err(dev, "Multiple bt hosts not supported\n"); + return -ENOMEM; + } + + bt_host = devm_kzalloc(dev, sizeof(*bt_host), GFP_KERNEL); + if (!bt_host) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Unable to find resources\n"); + rc = -ENXIO; + goto out_free; + } + + bt_host->base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!bt_host->base) { + rc = -ENOMEM; + goto out_free; + } + + init_waitqueue_head(&bt_host->queue); + + bt_host_miscdev.parent = dev; + rc = misc_register(&bt_host_miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + goto out_unmap; + } + + init_timer(&bt_host->poll_timer); + bt_host->poll_timer.function = poll_timer; + bt_host->poll_timer.expires = jiffies + msecs_to_jiffies(10); + add_timer(&bt_host->poll_timer); + + iowrite32((BT_IO_BASE << BT_CR0_IO_BASE) | + (BT_IRQ << BT_CR0_IRQ) | + BT_CR0_EN_CLR_SLV_RDP | + BT_CR0_EN_CLR_SLV_WRP | + BT_CR0_ENABLE_IBT, + bt_host->base + BT_CR0); + + clr_b_busy(bt_host); + + return 0; + +out_unmap: + devm_iounmap(&pdev->dev, bt_host->base); + +out_free: + devm_kfree(dev, bt_host); + return rc; + +} + +static int bt_host_remove(struct platform_device *pdev) +{ + del_timer_sync(&bt_host->poll_timer); + misc_deregister(&bt_host_miscdev); + devm_iounmap(&pdev->dev, bt_host->base); + devm_kfree(&pdev->dev, bt_host); + bt_host = NULL; + + return 0; +} + +static const struct of_device_id bt_host_match[] = { + { .compatible = "bt-host" }, + { }, +}; + +static struct platform_driver bt_host_driver = { + .driver = { + .name = "bt-host", + .owner = THIS_MODULE, + .of_match_table = bt_host_match, + }, + .probe = bt_host_probe, + .remove = bt_host_remove, +}; + +static int __init bt_host_init(void) +{ + int rc; + + rc = alloc_chrdev_region(&bt_host_devt, 0, BT_NUM_DEVS, "bt"); + if (rc) { + pr_err("bt-host: Could not allocate chardev region\n"); + return rc; + } + + rc = platform_driver_register(&bt_host_driver); + if (rc) { + pr_err("bt-host: Could not register platform device\n"); + goto out_chardev; + } + + return 0; + +out_chardev: + unregister_chrdev_region(bt_host_devt, BT_NUM_DEVS); + return rc; +} +module_init(bt_host_init); + +static void __exit bt_host_exit(void) +{ + platform_driver_unregister(&bt_host_driver); +} +module_exit(bt_host_exit); + +MODULE_DEVICE_TABLE(of, bt_host_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alistair Popple "); +MODULE_DESCRIPTION("Linux device interface to the BT interface");