soc: add aspeed folder and misc drivers
authorPatrick Venture <venture@google.com>
Mon, 22 Apr 2019 17:54:19 +0000 (10:54 -0700)
committerOlof Johansson <olof@lixom.net>
Mon, 29 Apr 2019 16:36:34 +0000 (09:36 -0700)
Create a SoC folder for the ASPEED parts and place the misc drivers
currently present into this folder.  These drivers are not generic part
drivers, but rather only apply to the ASPEED SoCs.

Signed-off-by: Patrick Venture <venture@google.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Olof Johansson <olof@lixom.net>
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/aspeed-lpc-ctrl.c [deleted file]
drivers/misc/aspeed-lpc-snoop.c [deleted file]
drivers/soc/Kconfig
drivers/soc/Makefile
drivers/soc/aspeed/Kconfig [new file with mode: 0644]
drivers/soc/aspeed/Makefile [new file with mode: 0644]
drivers/soc/aspeed/aspeed-lpc-ctrl.c [new file with mode: 0644]
drivers/soc/aspeed/aspeed-lpc-snoop.c [new file with mode: 0644]

index 42ab8ec92a0464ab1806a088983fc863b25f4cb4..b80cb6af0cb4d1acacabbb1ec37ee4d212cb5d76 100644 (file)
@@ -496,22 +496,6 @@ config VEXPRESS_SYSCFG
          bus. System Configuration interface is one of the possible means
          of generating transactions on this bus.
 
-config ASPEED_LPC_CTRL
-       depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
-       tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control"
-       ---help---
-         Control Aspeed ast2400/2500 HOST LPC to BMC mappings through
-         ioctl()s, the driver also provides a read/write interface to a BMC ram
-         region where the host LPC read/write region can be buffered.
-
-config ASPEED_LPC_SNOOP
-       tristate "Aspeed ast2500 HOST LPC snoop support"
-       depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
-       help
-         Provides a driver to control the LPC snoop interface which
-         allows the BMC to listen on and save the data written by
-         the host to an arbitrary LPC I/O port.
-
 config PCI_ENDPOINT_TEST
        depends on PCI
        select CRC32
index d5b7d3404dc78a515c597711b358b51912bd3e77..b9affcdaa3d6ebe32e383aa8e6cb0288055d47e3 100644 (file)
@@ -54,8 +54,6 @@ obj-$(CONFIG_GENWQE)          += genwqe/
 obj-$(CONFIG_ECHO)             += echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)  += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)         += cxl/
-obj-$(CONFIG_ASPEED_LPC_CTRL)  += aspeed-lpc-ctrl.o
-obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
 obj-$(CONFIG_PCI_ENDPOINT_TEST)        += pci_endpoint_test.o
 obj-$(CONFIG_OCXL)             += ocxl/
 obj-y                          += cardreader/
diff --git a/drivers/misc/aspeed-lpc-ctrl.c b/drivers/misc/aspeed-lpc-ctrl.c
deleted file mode 100644 (file)
index a024f80..0000000
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright 2017 IBM Corporation
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version
- * 2 of the License, or (at your option) any later version.
- */
-
-#include <linux/clk.h>
-#include <linux/mfd/syscon.h>
-#include <linux/miscdevice.h>
-#include <linux/mm.h>
-#include <linux/module.h>
-#include <linux/of_address.h>
-#include <linux/platform_device.h>
-#include <linux/poll.h>
-#include <linux/regmap.h>
-
-#include <linux/aspeed-lpc-ctrl.h>
-
-#define DEVICE_NAME    "aspeed-lpc-ctrl"
-
-#define HICR5 0x0
-#define HICR5_ENL2H    BIT(8)
-#define HICR5_ENFWH    BIT(10)
-
-#define HICR7 0x8
-#define HICR8 0xc
-
-struct aspeed_lpc_ctrl {
-       struct miscdevice       miscdev;
-       struct regmap           *regmap;
-       struct clk              *clk;
-       phys_addr_t             mem_base;
-       resource_size_t         mem_size;
-       u32             pnor_size;
-       u32             pnor_base;
-};
-
-static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file)
-{
-       return container_of(file->private_data, struct aspeed_lpc_ctrl,
-                       miscdev);
-}
-
-static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma)
-{
-       struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
-       unsigned long vsize = vma->vm_end - vma->vm_start;
-       pgprot_t prot = vma->vm_page_prot;
-
-       if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size)
-               return -EINVAL;
-
-       /* ast2400/2500 AHB accesses are not cache coherent */
-       prot = pgprot_noncached(prot);
-
-       if (remap_pfn_range(vma, vma->vm_start,
-               (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff,
-               vsize, prot))
-               return -EAGAIN;
-
-       return 0;
-}
-
-static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd,
-               unsigned long param)
-{
-       struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
-       void __user *p = (void __user *)param;
-       struct aspeed_lpc_ctrl_mapping map;
-       u32 addr;
-       u32 size;
-       long rc;
-
-       if (copy_from_user(&map, p, sizeof(map)))
-               return -EFAULT;
-
-       if (map.flags != 0)
-               return -EINVAL;
-
-       switch (cmd) {
-       case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
-               /* The flash windows don't report their size */
-               if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY)
-                       return -EINVAL;
-
-               /* Support more than one window id in the future */
-               if (map.window_id != 0)
-                       return -EINVAL;
-
-               map.size = lpc_ctrl->mem_size;
-
-               return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0;
-       case ASPEED_LPC_CTRL_IOCTL_MAP:
-
-               /*
-                * The top half of HICR7 is the MSB of the BMC address of the
-                * mapping.
-                * The bottom half of HICR7 is the MSB of the HOST LPC
-                * firmware space address of the mapping.
-                *
-                * The 1 bits in the top of half of HICR8 represent the bits
-                * (in the requested address) that should be ignored and
-                * replaced with those from the top half of HICR7.
-                * The 1 bits in the bottom half of HICR8 represent the bits
-                * (in the requested address) that should be kept and pass
-                * into the BMC address space.
-                */
-
-               /*
-                * It doesn't make sense to talk about a size or offset with
-                * low 16 bits set. Both HICR7 and HICR8 talk about the top 16
-                * bits of addresses and sizes.
-                */
-
-               if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff))
-                       return -EINVAL;
-
-               /*
-                * Because of the way the masks work in HICR8 offset has to
-                * be a multiple of size.
-                */
-               if (map.offset & (map.size - 1))
-                       return -EINVAL;
-
-               if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) {
-                       addr = lpc_ctrl->pnor_base;
-                       size = lpc_ctrl->pnor_size;
-               } else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) {
-                       addr = lpc_ctrl->mem_base;
-                       size = lpc_ctrl->mem_size;
-               } else {
-                       return -EINVAL;
-               }
-
-               /* Check overflow first! */
-               if (map.offset + map.size < map.offset ||
-                       map.offset + map.size > size)
-                       return -EINVAL;
-
-               if (map.size == 0 || map.size > size)
-                       return -EINVAL;
-
-               addr += map.offset;
-
-               /*
-                * addr (host lpc address) is safe regardless of values. This
-                * simply changes the address the host has to request on its
-                * side of the LPC bus. This cannot impact the hosts own
-                * memory space by surprise as LPC specific accessors are
-                * required. The only strange thing that could be done is
-                * setting the lower 16 bits but the shift takes care of that.
-                */
-
-               rc = regmap_write(lpc_ctrl->regmap, HICR7,
-                               (addr | (map.addr >> 16)));
-               if (rc)
-                       return rc;
-
-               rc = regmap_write(lpc_ctrl->regmap, HICR8,
-                               (~(map.size - 1)) | ((map.size >> 16) - 1));
-               if (rc)
-                       return rc;
-
-               /*
-                * Enable LPC FHW cycles. This is required for the host to
-                * access the regions specified.
-                */
-               return regmap_update_bits(lpc_ctrl->regmap, HICR5,
-                               HICR5_ENFWH | HICR5_ENL2H,
-                               HICR5_ENFWH | HICR5_ENL2H);
-       }
-
-       return -EINVAL;
-}
-
-static const struct file_operations aspeed_lpc_ctrl_fops = {
-       .owner          = THIS_MODULE,
-       .mmap           = aspeed_lpc_ctrl_mmap,
-       .unlocked_ioctl = aspeed_lpc_ctrl_ioctl,
-};
-
-static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
-{
-       struct aspeed_lpc_ctrl *lpc_ctrl;
-       struct device_node *node;
-       struct resource resm;
-       struct device *dev;
-       int rc;
-
-       dev = &pdev->dev;
-
-       lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL);
-       if (!lpc_ctrl)
-               return -ENOMEM;
-
-       node = of_parse_phandle(dev->of_node, "flash", 0);
-       if (!node) {
-               dev_err(dev, "Didn't find host pnor flash node\n");
-               return -ENODEV;
-       }
-
-       rc = of_address_to_resource(node, 1, &resm);
-       of_node_put(node);
-       if (rc) {
-               dev_err(dev, "Couldn't address to resource for flash\n");
-               return rc;
-       }
-
-       lpc_ctrl->pnor_size = resource_size(&resm);
-       lpc_ctrl->pnor_base = resm.start;
-
-       dev_set_drvdata(&pdev->dev, lpc_ctrl);
-
-       node = of_parse_phandle(dev->of_node, "memory-region", 0);
-       if (!node) {
-               dev_err(dev, "Didn't find reserved memory\n");
-               return -EINVAL;
-       }
-
-       rc = of_address_to_resource(node, 0, &resm);
-       of_node_put(node);
-       if (rc) {
-               dev_err(dev, "Couldn't address to resource for reserved memory\n");
-               return -ENOMEM;
-       }
-
-       lpc_ctrl->mem_size = resource_size(&resm);
-       lpc_ctrl->mem_base = resm.start;
-
-       lpc_ctrl->regmap = syscon_node_to_regmap(
-                       pdev->dev.parent->of_node);
-       if (IS_ERR(lpc_ctrl->regmap)) {
-               dev_err(dev, "Couldn't get regmap\n");
-               return -ENODEV;
-       }
-
-       lpc_ctrl->clk = devm_clk_get(dev, NULL);
-       if (IS_ERR(lpc_ctrl->clk)) {
-               dev_err(dev, "couldn't get clock\n");
-               return PTR_ERR(lpc_ctrl->clk);
-       }
-       rc = clk_prepare_enable(lpc_ctrl->clk);
-       if (rc) {
-               dev_err(dev, "couldn't enable clock\n");
-               return rc;
-       }
-
-       lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
-       lpc_ctrl->miscdev.name = DEVICE_NAME;
-       lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
-       lpc_ctrl->miscdev.parent = dev;
-       rc = misc_register(&lpc_ctrl->miscdev);
-       if (rc) {
-               dev_err(dev, "Unable to register device\n");
-               goto err;
-       }
-
-       dev_info(dev, "Loaded at %pr\n", &resm);
-
-       return 0;
-
-err:
-       clk_disable_unprepare(lpc_ctrl->clk);
-       return rc;
-}
-
-static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
-{
-       struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev);
-
-       misc_deregister(&lpc_ctrl->miscdev);
-       clk_disable_unprepare(lpc_ctrl->clk);
-
-       return 0;
-}
-
-static const struct of_device_id aspeed_lpc_ctrl_match[] = {
-       { .compatible = "aspeed,ast2400-lpc-ctrl" },
-       { .compatible = "aspeed,ast2500-lpc-ctrl" },
-       { },
-};
-
-static struct platform_driver aspeed_lpc_ctrl_driver = {
-       .driver = {
-               .name           = DEVICE_NAME,
-               .of_match_table = aspeed_lpc_ctrl_match,
-       },
-       .probe = aspeed_lpc_ctrl_probe,
-       .remove = aspeed_lpc_ctrl_remove,
-};
-
-module_platform_driver(aspeed_lpc_ctrl_driver);
-
-MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>");
-MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings");
diff --git a/drivers/misc/aspeed-lpc-snoop.c b/drivers/misc/aspeed-lpc-snoop.c
deleted file mode 100644 (file)
index 2feb434..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * 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
- * as published by the Free Software Foundation; either version
- * 2 of the License, or (at your option) any later version.
- *
- * Provides a simple driver to control the ASPEED LPC snoop interface which
- * allows the BMC to listen on and save the data written by
- * the host to an arbitrary LPC I/O port.
- *
- * Typically used by the BMC to "watch" host boot progress via port
- * 0x80 writes made by the BIOS during the boot process.
- */
-
-#include <linux/bitops.h>
-#include <linux/interrupt.h>
-#include <linux/fs.h>
-#include <linux/kfifo.h>
-#include <linux/mfd/syscon.h>
-#include <linux/miscdevice.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_device.h>
-#include <linux/platform_device.h>
-#include <linux/poll.h>
-#include <linux/regmap.h>
-
-#define DEVICE_NAME    "aspeed-lpc-snoop"
-
-#define NUM_SNOOP_CHANNELS 2
-#define SNOOP_FIFO_SIZE 2048
-
-#define HICR5  0x0
-#define HICR5_EN_SNP0W         BIT(0)
-#define HICR5_ENINT_SNP0W      BIT(1)
-#define HICR5_EN_SNP1W         BIT(2)
-#define HICR5_ENINT_SNP1W      BIT(3)
-
-#define HICR6  0x4
-#define HICR6_STR_SNP0W                BIT(0)
-#define HICR6_STR_SNP1W                BIT(1)
-#define SNPWADR        0x10
-#define SNPWADR_CH0_MASK       GENMASK(15, 0)
-#define SNPWADR_CH0_SHIFT      0
-#define SNPWADR_CH1_MASK       GENMASK(31, 16)
-#define SNPWADR_CH1_SHIFT      16
-#define SNPWDR 0x14
-#define SNPWDR_CH0_MASK                GENMASK(7, 0)
-#define SNPWDR_CH0_SHIFT       0
-#define SNPWDR_CH1_MASK                GENMASK(15, 8)
-#define SNPWDR_CH1_SHIFT       8
-#define HICRB  0x80
-#define HICRB_ENSNP0D          BIT(14)
-#define HICRB_ENSNP1D          BIT(15)
-
-struct aspeed_lpc_snoop_model_data {
-       /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
-        * can use them.
-        */
-       unsigned int has_hicrb_ensnp;
-};
-
-struct aspeed_lpc_snoop_channel {
-       struct kfifo            fifo;
-       wait_queue_head_t       wq;
-       struct miscdevice       miscdev;
-};
-
-struct aspeed_lpc_snoop {
-       struct regmap           *regmap;
-       int                     irq;
-       struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS];
-};
-
-static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file)
-{
-       return container_of(file->private_data,
-                           struct aspeed_lpc_snoop_channel,
-                           miscdev);
-}
-
-static ssize_t snoop_file_read(struct file *file, char __user *buffer,
-                               size_t count, loff_t *ppos)
-{
-       struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file);
-       unsigned int copied;
-       int ret = 0;
-
-       if (kfifo_is_empty(&chan->fifo)) {
-               if (file->f_flags & O_NONBLOCK)
-                       return -EAGAIN;
-               ret = wait_event_interruptible(chan->wq,
-                               !kfifo_is_empty(&chan->fifo));
-               if (ret == -ERESTARTSYS)
-                       return -EINTR;
-       }
-       ret = kfifo_to_user(&chan->fifo, buffer, count, &copied);
-
-       return ret ? ret : copied;
-}
-
-static unsigned int snoop_file_poll(struct file *file,
-                                   struct poll_table_struct *pt)
-{
-       struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file);
-
-       poll_wait(file, &chan->wq, pt);
-       return !kfifo_is_empty(&chan->fifo) ? POLLIN : 0;
-}
-
-static const struct file_operations snoop_fops = {
-       .owner  = THIS_MODULE,
-       .read   = snoop_file_read,
-       .poll   = snoop_file_poll,
-       .llseek = noop_llseek,
-};
-
-/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
-static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val)
-{
-       if (!kfifo_initialized(&chan->fifo))
-               return;
-       if (kfifo_is_full(&chan->fifo))
-               kfifo_skip(&chan->fifo);
-       kfifo_put(&chan->fifo, val);
-       wake_up_interruptible(&chan->wq);
-}
-
-static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg)
-{
-       struct aspeed_lpc_snoop *lpc_snoop = arg;
-       u32 reg, data;
-
-       if (regmap_read(lpc_snoop->regmap, HICR6, &reg))
-               return IRQ_NONE;
-
-       /* Check if one of the snoop channels is interrupting */
-       reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W);
-       if (!reg)
-               return IRQ_NONE;
-
-       /* Ack pending IRQs */
-       regmap_write(lpc_snoop->regmap, HICR6, reg);
-
-       /* Read and save most recent snoop'ed data byte to FIFO */
-       regmap_read(lpc_snoop->regmap, SNPWDR, &data);
-
-       if (reg & HICR6_STR_SNP0W) {
-               u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT;
-
-               put_fifo_with_discard(&lpc_snoop->chan[0], val);
-       }
-       if (reg & HICR6_STR_SNP1W) {
-               u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT;
-
-               put_fifo_with_discard(&lpc_snoop->chan[1], val);
-       }
-
-       return IRQ_HANDLED;
-}
-
-static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
-                                      struct platform_device *pdev)
-{
-       struct device *dev = &pdev->dev;
-       int rc;
-
-       lpc_snoop->irq = platform_get_irq(pdev, 0);
-       if (!lpc_snoop->irq)
-               return -ENODEV;
-
-       rc = devm_request_irq(dev, lpc_snoop->irq,
-                             aspeed_lpc_snoop_irq, IRQF_SHARED,
-                             DEVICE_NAME, lpc_snoop);
-       if (rc < 0) {
-               dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq);
-               lpc_snoop->irq = 0;
-               return rc;
-       }
-
-       return 0;
-}
-
-static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
-                                  struct device *dev,
-                                  int channel, u16 lpc_port)
-{
-       int rc = 0;
-       u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en;
-       const struct aspeed_lpc_snoop_model_data *model_data =
-               of_device_get_match_data(dev);
-
-       init_waitqueue_head(&lpc_snoop->chan[channel].wq);
-       /* Create FIFO datastructure */
-       rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo,
-                        SNOOP_FIFO_SIZE, GFP_KERNEL);
-       if (rc)
-               return rc;
-
-       lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR;
-       lpc_snoop->chan[channel].miscdev.name =
-               devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel);
-       lpc_snoop->chan[channel].miscdev.fops = &snoop_fops;
-       lpc_snoop->chan[channel].miscdev.parent = dev;
-       rc = misc_register(&lpc_snoop->chan[channel].miscdev);
-       if (rc)
-               return rc;
-
-       /* Enable LPC snoop channel at requested port */
-       switch (channel) {
-       case 0:
-               hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W;
-               snpwadr_mask = SNPWADR_CH0_MASK;
-               snpwadr_shift = SNPWADR_CH0_SHIFT;
-               hicrb_en = HICRB_ENSNP0D;
-               break;
-       case 1:
-               hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W;
-               snpwadr_mask = SNPWADR_CH1_MASK;
-               snpwadr_shift = SNPWADR_CH1_SHIFT;
-               hicrb_en = HICRB_ENSNP1D;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en);
-       regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask,
-                          lpc_port << snpwadr_shift);
-       if (model_data->has_hicrb_ensnp)
-               regmap_update_bits(lpc_snoop->regmap, HICRB,
-                               hicrb_en, hicrb_en);
-
-       return rc;
-}
-
-static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
-                                    int channel)
-{
-       switch (channel) {
-       case 0:
-               regmap_update_bits(lpc_snoop->regmap, HICR5,
-                                  HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
-                                  0);
-               break;
-       case 1:
-               regmap_update_bits(lpc_snoop->regmap, HICR5,
-                                  HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
-                                  0);
-               break;
-       default:
-               return;
-       }
-
-       kfifo_free(&lpc_snoop->chan[channel].fifo);
-       misc_deregister(&lpc_snoop->chan[channel].miscdev);
-}
-
-static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
-{
-       struct aspeed_lpc_snoop *lpc_snoop;
-       struct device *dev;
-       u32 port;
-       int rc;
-
-       dev = &pdev->dev;
-
-       lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL);
-       if (!lpc_snoop)
-               return -ENOMEM;
-
-       lpc_snoop->regmap = syscon_node_to_regmap(
-                       pdev->dev.parent->of_node);
-       if (IS_ERR(lpc_snoop->regmap)) {
-               dev_err(dev, "Couldn't get regmap\n");
-               return -ENODEV;
-       }
-
-       dev_set_drvdata(&pdev->dev, lpc_snoop);
-
-       rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port);
-       if (rc) {
-               dev_err(dev, "no snoop ports configured\n");
-               return -ENODEV;
-       }
-
-       rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
-       if (rc)
-               return rc;
-
-       rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port);
-       if (rc)
-               return rc;
-
-       /* Configuration of 2nd snoop channel port is optional */
-       if (of_property_read_u32_index(dev->of_node, "snoop-ports",
-                                      1, &port) == 0) {
-               rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port);
-               if (rc)
-                       aspeed_lpc_disable_snoop(lpc_snoop, 0);
-       }
-
-       return rc;
-}
-
-static int aspeed_lpc_snoop_remove(struct platform_device *pdev)
-{
-       struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
-
-       /* Disable both snoop channels */
-       aspeed_lpc_disable_snoop(lpc_snoop, 0);
-       aspeed_lpc_disable_snoop(lpc_snoop, 1);
-
-       return 0;
-}
-
-static const struct aspeed_lpc_snoop_model_data ast2400_model_data = {
-       .has_hicrb_ensnp = 0,
-};
-
-static const struct aspeed_lpc_snoop_model_data ast2500_model_data = {
-       .has_hicrb_ensnp = 1,
-};
-
-static const struct of_device_id aspeed_lpc_snoop_match[] = {
-       { .compatible = "aspeed,ast2400-lpc-snoop",
-         .data = &ast2400_model_data },
-       { .compatible = "aspeed,ast2500-lpc-snoop",
-         .data = &ast2500_model_data },
-       { },
-};
-
-static struct platform_driver aspeed_lpc_snoop_driver = {
-       .driver = {
-               .name           = DEVICE_NAME,
-               .of_match_table = aspeed_lpc_snoop_match,
-       },
-       .probe = aspeed_lpc_snoop_probe,
-       .remove = aspeed_lpc_snoop_remove,
-};
-
-module_platform_driver(aspeed_lpc_snoop_driver);
-
-MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match);
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
-MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");
index c07b4a85253f223bf46bf35c7199ed21e31dd643..b750a88547c780a64f6c262a33ac13697d4f5320 100644 (file)
@@ -2,6 +2,7 @@ menu "SOC (System On Chip) specific Drivers"
 
 source "drivers/soc/actions/Kconfig"
 source "drivers/soc/amlogic/Kconfig"
+source "drivers/soc/aspeed/Kconfig"
 source "drivers/soc/atmel/Kconfig"
 source "drivers/soc/bcm/Kconfig"
 source "drivers/soc/fsl/Kconfig"
index 90b686e586c667e381121fa038903ab54099b6c8..814128fe479f974991a26ef35db86bac7d61ea22 100644 (file)
@@ -4,6 +4,7 @@
 #
 
 obj-$(CONFIG_ARCH_ACTIONS)     += actions/
+obj-$(CONFIG_ARCH_ASPEED)      += aspeed/
 obj-$(CONFIG_ARCH_AT91)                += atmel/
 obj-y                          += bcm/
 obj-$(CONFIG_ARCH_DOVE)                += dove/
diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig
new file mode 100644 (file)
index 0000000..457282c
--- /dev/null
@@ -0,0 +1,19 @@
+menu "Aspeed SoC drivers"
+
+config ASPEED_LPC_CTRL
+       depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
+       tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control"
+       ---help---
+         Control Aspeed ast2400/2500 HOST LPC to BMC mappings through
+         ioctl()s, the driver also provides a read/write interface to a BMC ram
+         region where the host LPC read/write region can be buffered.
+
+config ASPEED_LPC_SNOOP
+       tristate "Aspeed ast2500 HOST LPC snoop support"
+       depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
+       help
+         Provides a driver to control the LPC snoop interface which
+         allows the BMC to listen on and save the data written by
+         the host to an arbitrary LPC I/O port.
+
+
diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile
new file mode 100644 (file)
index 0000000..cfaa9ad
--- /dev/null
@@ -0,0 +1,2 @@
+obj-$(CONFIG_ASPEED_LPC_CTRL)  += aspeed-lpc-ctrl.o
+obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
diff --git a/drivers/soc/aspeed/aspeed-lpc-ctrl.c b/drivers/soc/aspeed/aspeed-lpc-ctrl.c
new file mode 100644 (file)
index 0000000..a024f80
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/regmap.h>
+
+#include <linux/aspeed-lpc-ctrl.h>
+
+#define DEVICE_NAME    "aspeed-lpc-ctrl"
+
+#define HICR5 0x0
+#define HICR5_ENL2H    BIT(8)
+#define HICR5_ENFWH    BIT(10)
+
+#define HICR7 0x8
+#define HICR8 0xc
+
+struct aspeed_lpc_ctrl {
+       struct miscdevice       miscdev;
+       struct regmap           *regmap;
+       struct clk              *clk;
+       phys_addr_t             mem_base;
+       resource_size_t         mem_size;
+       u32             pnor_size;
+       u32             pnor_base;
+};
+
+static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file)
+{
+       return container_of(file->private_data, struct aspeed_lpc_ctrl,
+                       miscdev);
+}
+
+static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
+       unsigned long vsize = vma->vm_end - vma->vm_start;
+       pgprot_t prot = vma->vm_page_prot;
+
+       if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size)
+               return -EINVAL;
+
+       /* ast2400/2500 AHB accesses are not cache coherent */
+       prot = pgprot_noncached(prot);
+
+       if (remap_pfn_range(vma, vma->vm_start,
+               (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff,
+               vsize, prot))
+               return -EAGAIN;
+
+       return 0;
+}
+
+static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd,
+               unsigned long param)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
+       void __user *p = (void __user *)param;
+       struct aspeed_lpc_ctrl_mapping map;
+       u32 addr;
+       u32 size;
+       long rc;
+
+       if (copy_from_user(&map, p, sizeof(map)))
+               return -EFAULT;
+
+       if (map.flags != 0)
+               return -EINVAL;
+
+       switch (cmd) {
+       case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
+               /* The flash windows don't report their size */
+               if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY)
+                       return -EINVAL;
+
+               /* Support more than one window id in the future */
+               if (map.window_id != 0)
+                       return -EINVAL;
+
+               map.size = lpc_ctrl->mem_size;
+
+               return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0;
+       case ASPEED_LPC_CTRL_IOCTL_MAP:
+
+               /*
+                * The top half of HICR7 is the MSB of the BMC address of the
+                * mapping.
+                * The bottom half of HICR7 is the MSB of the HOST LPC
+                * firmware space address of the mapping.
+                *
+                * The 1 bits in the top of half of HICR8 represent the bits
+                * (in the requested address) that should be ignored and
+                * replaced with those from the top half of HICR7.
+                * The 1 bits in the bottom half of HICR8 represent the bits
+                * (in the requested address) that should be kept and pass
+                * into the BMC address space.
+                */
+
+               /*
+                * It doesn't make sense to talk about a size or offset with
+                * low 16 bits set. Both HICR7 and HICR8 talk about the top 16
+                * bits of addresses and sizes.
+                */
+
+               if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff))
+                       return -EINVAL;
+
+               /*
+                * Because of the way the masks work in HICR8 offset has to
+                * be a multiple of size.
+                */
+               if (map.offset & (map.size - 1))
+                       return -EINVAL;
+
+               if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) {
+                       addr = lpc_ctrl->pnor_base;
+                       size = lpc_ctrl->pnor_size;
+               } else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) {
+                       addr = lpc_ctrl->mem_base;
+                       size = lpc_ctrl->mem_size;
+               } else {
+                       return -EINVAL;
+               }
+
+               /* Check overflow first! */
+               if (map.offset + map.size < map.offset ||
+                       map.offset + map.size > size)
+                       return -EINVAL;
+
+               if (map.size == 0 || map.size > size)
+                       return -EINVAL;
+
+               addr += map.offset;
+
+               /*
+                * addr (host lpc address) is safe regardless of values. This
+                * simply changes the address the host has to request on its
+                * side of the LPC bus. This cannot impact the hosts own
+                * memory space by surprise as LPC specific accessors are
+                * required. The only strange thing that could be done is
+                * setting the lower 16 bits but the shift takes care of that.
+                */
+
+               rc = regmap_write(lpc_ctrl->regmap, HICR7,
+                               (addr | (map.addr >> 16)));
+               if (rc)
+                       return rc;
+
+               rc = regmap_write(lpc_ctrl->regmap, HICR8,
+                               (~(map.size - 1)) | ((map.size >> 16) - 1));
+               if (rc)
+                       return rc;
+
+               /*
+                * Enable LPC FHW cycles. This is required for the host to
+                * access the regions specified.
+                */
+               return regmap_update_bits(lpc_ctrl->regmap, HICR5,
+                               HICR5_ENFWH | HICR5_ENL2H,
+                               HICR5_ENFWH | HICR5_ENL2H);
+       }
+
+       return -EINVAL;
+}
+
+static const struct file_operations aspeed_lpc_ctrl_fops = {
+       .owner          = THIS_MODULE,
+       .mmap           = aspeed_lpc_ctrl_mmap,
+       .unlocked_ioctl = aspeed_lpc_ctrl_ioctl,
+};
+
+static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl;
+       struct device_node *node;
+       struct resource resm;
+       struct device *dev;
+       int rc;
+
+       dev = &pdev->dev;
+
+       lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL);
+       if (!lpc_ctrl)
+               return -ENOMEM;
+
+       node = of_parse_phandle(dev->of_node, "flash", 0);
+       if (!node) {
+               dev_err(dev, "Didn't find host pnor flash node\n");
+               return -ENODEV;
+       }
+
+       rc = of_address_to_resource(node, 1, &resm);
+       of_node_put(node);
+       if (rc) {
+               dev_err(dev, "Couldn't address to resource for flash\n");
+               return rc;
+       }
+
+       lpc_ctrl->pnor_size = resource_size(&resm);
+       lpc_ctrl->pnor_base = resm.start;
+
+       dev_set_drvdata(&pdev->dev, lpc_ctrl);
+
+       node = of_parse_phandle(dev->of_node, "memory-region", 0);
+       if (!node) {
+               dev_err(dev, "Didn't find reserved memory\n");
+               return -EINVAL;
+       }
+
+       rc = of_address_to_resource(node, 0, &resm);
+       of_node_put(node);
+       if (rc) {
+               dev_err(dev, "Couldn't address to resource for reserved memory\n");
+               return -ENOMEM;
+       }
+
+       lpc_ctrl->mem_size = resource_size(&resm);
+       lpc_ctrl->mem_base = resm.start;
+
+       lpc_ctrl->regmap = syscon_node_to_regmap(
+                       pdev->dev.parent->of_node);
+       if (IS_ERR(lpc_ctrl->regmap)) {
+               dev_err(dev, "Couldn't get regmap\n");
+               return -ENODEV;
+       }
+
+       lpc_ctrl->clk = devm_clk_get(dev, NULL);
+       if (IS_ERR(lpc_ctrl->clk)) {
+               dev_err(dev, "couldn't get clock\n");
+               return PTR_ERR(lpc_ctrl->clk);
+       }
+       rc = clk_prepare_enable(lpc_ctrl->clk);
+       if (rc) {
+               dev_err(dev, "couldn't enable clock\n");
+               return rc;
+       }
+
+       lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
+       lpc_ctrl->miscdev.name = DEVICE_NAME;
+       lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
+       lpc_ctrl->miscdev.parent = dev;
+       rc = misc_register(&lpc_ctrl->miscdev);
+       if (rc) {
+               dev_err(dev, "Unable to register device\n");
+               goto err;
+       }
+
+       dev_info(dev, "Loaded at %pr\n", &resm);
+
+       return 0;
+
+err:
+       clk_disable_unprepare(lpc_ctrl->clk);
+       return rc;
+}
+
+static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev);
+
+       misc_deregister(&lpc_ctrl->miscdev);
+       clk_disable_unprepare(lpc_ctrl->clk);
+
+       return 0;
+}
+
+static const struct of_device_id aspeed_lpc_ctrl_match[] = {
+       { .compatible = "aspeed,ast2400-lpc-ctrl" },
+       { .compatible = "aspeed,ast2500-lpc-ctrl" },
+       { },
+};
+
+static struct platform_driver aspeed_lpc_ctrl_driver = {
+       .driver = {
+               .name           = DEVICE_NAME,
+               .of_match_table = aspeed_lpc_ctrl_match,
+       },
+       .probe = aspeed_lpc_ctrl_probe,
+       .remove = aspeed_lpc_ctrl_remove,
+};
+
+module_platform_driver(aspeed_lpc_ctrl_driver);
+
+MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>");
+MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings");
diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c
new file mode 100644 (file)
index 0000000..2feb434
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * 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
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Provides a simple driver to control the ASPEED LPC snoop interface which
+ * allows the BMC to listen on and save the data written by
+ * the host to an arbitrary LPC I/O port.
+ *
+ * Typically used by the BMC to "watch" host boot progress via port
+ * 0x80 writes made by the BIOS during the boot process.
+ */
+
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/kfifo.h>
+#include <linux/mfd/syscon.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/regmap.h>
+
+#define DEVICE_NAME    "aspeed-lpc-snoop"
+
+#define NUM_SNOOP_CHANNELS 2
+#define SNOOP_FIFO_SIZE 2048
+
+#define HICR5  0x0
+#define HICR5_EN_SNP0W         BIT(0)
+#define HICR5_ENINT_SNP0W      BIT(1)
+#define HICR5_EN_SNP1W         BIT(2)
+#define HICR5_ENINT_SNP1W      BIT(3)
+
+#define HICR6  0x4
+#define HICR6_STR_SNP0W                BIT(0)
+#define HICR6_STR_SNP1W                BIT(1)
+#define SNPWADR        0x10
+#define SNPWADR_CH0_MASK       GENMASK(15, 0)
+#define SNPWADR_CH0_SHIFT      0
+#define SNPWADR_CH1_MASK       GENMASK(31, 16)
+#define SNPWADR_CH1_SHIFT      16
+#define SNPWDR 0x14
+#define SNPWDR_CH0_MASK                GENMASK(7, 0)
+#define SNPWDR_CH0_SHIFT       0
+#define SNPWDR_CH1_MASK                GENMASK(15, 8)
+#define SNPWDR_CH1_SHIFT       8
+#define HICRB  0x80
+#define HICRB_ENSNP0D          BIT(14)
+#define HICRB_ENSNP1D          BIT(15)
+
+struct aspeed_lpc_snoop_model_data {
+       /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
+        * can use them.
+        */
+       unsigned int has_hicrb_ensnp;
+};
+
+struct aspeed_lpc_snoop_channel {
+       struct kfifo            fifo;
+       wait_queue_head_t       wq;
+       struct miscdevice       miscdev;
+};
+
+struct aspeed_lpc_snoop {
+       struct regmap           *regmap;
+       int                     irq;
+       struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS];
+};
+
+static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file)
+{
+       return container_of(file->private_data,
+                           struct aspeed_lpc_snoop_channel,
+                           miscdev);
+}
+
+static ssize_t snoop_file_read(struct file *file, char __user *buffer,
+                               size_t count, loff_t *ppos)
+{
+       struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file);
+       unsigned int copied;
+       int ret = 0;
+
+       if (kfifo_is_empty(&chan->fifo)) {
+               if (file->f_flags & O_NONBLOCK)
+                       return -EAGAIN;
+               ret = wait_event_interruptible(chan->wq,
+                               !kfifo_is_empty(&chan->fifo));
+               if (ret == -ERESTARTSYS)
+                       return -EINTR;
+       }
+       ret = kfifo_to_user(&chan->fifo, buffer, count, &copied);
+
+       return ret ? ret : copied;
+}
+
+static unsigned int snoop_file_poll(struct file *file,
+                                   struct poll_table_struct *pt)
+{
+       struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file);
+
+       poll_wait(file, &chan->wq, pt);
+       return !kfifo_is_empty(&chan->fifo) ? POLLIN : 0;
+}
+
+static const struct file_operations snoop_fops = {
+       .owner  = THIS_MODULE,
+       .read   = snoop_file_read,
+       .poll   = snoop_file_poll,
+       .llseek = noop_llseek,
+};
+
+/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
+static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val)
+{
+       if (!kfifo_initialized(&chan->fifo))
+               return;
+       if (kfifo_is_full(&chan->fifo))
+               kfifo_skip(&chan->fifo);
+       kfifo_put(&chan->fifo, val);
+       wake_up_interruptible(&chan->wq);
+}
+
+static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg)
+{
+       struct aspeed_lpc_snoop *lpc_snoop = arg;
+       u32 reg, data;
+
+       if (regmap_read(lpc_snoop->regmap, HICR6, &reg))
+               return IRQ_NONE;
+
+       /* Check if one of the snoop channels is interrupting */
+       reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W);
+       if (!reg)
+               return IRQ_NONE;
+
+       /* Ack pending IRQs */
+       regmap_write(lpc_snoop->regmap, HICR6, reg);
+
+       /* Read and save most recent snoop'ed data byte to FIFO */
+       regmap_read(lpc_snoop->regmap, SNPWDR, &data);
+
+       if (reg & HICR6_STR_SNP0W) {
+               u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT;
+
+               put_fifo_with_discard(&lpc_snoop->chan[0], val);
+       }
+       if (reg & HICR6_STR_SNP1W) {
+               u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT;
+
+               put_fifo_with_discard(&lpc_snoop->chan[1], val);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
+                                      struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       int rc;
+
+       lpc_snoop->irq = platform_get_irq(pdev, 0);
+       if (!lpc_snoop->irq)
+               return -ENODEV;
+
+       rc = devm_request_irq(dev, lpc_snoop->irq,
+                             aspeed_lpc_snoop_irq, IRQF_SHARED,
+                             DEVICE_NAME, lpc_snoop);
+       if (rc < 0) {
+               dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq);
+               lpc_snoop->irq = 0;
+               return rc;
+       }
+
+       return 0;
+}
+
+static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
+                                  struct device *dev,
+                                  int channel, u16 lpc_port)
+{
+       int rc = 0;
+       u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en;
+       const struct aspeed_lpc_snoop_model_data *model_data =
+               of_device_get_match_data(dev);
+
+       init_waitqueue_head(&lpc_snoop->chan[channel].wq);
+       /* Create FIFO datastructure */
+       rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo,
+                        SNOOP_FIFO_SIZE, GFP_KERNEL);
+       if (rc)
+               return rc;
+
+       lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR;
+       lpc_snoop->chan[channel].miscdev.name =
+               devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel);
+       lpc_snoop->chan[channel].miscdev.fops = &snoop_fops;
+       lpc_snoop->chan[channel].miscdev.parent = dev;
+       rc = misc_register(&lpc_snoop->chan[channel].miscdev);
+       if (rc)
+               return rc;
+
+       /* Enable LPC snoop channel at requested port */
+       switch (channel) {
+       case 0:
+               hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W;
+               snpwadr_mask = SNPWADR_CH0_MASK;
+               snpwadr_shift = SNPWADR_CH0_SHIFT;
+               hicrb_en = HICRB_ENSNP0D;
+               break;
+       case 1:
+               hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W;
+               snpwadr_mask = SNPWADR_CH1_MASK;
+               snpwadr_shift = SNPWADR_CH1_SHIFT;
+               hicrb_en = HICRB_ENSNP1D;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en);
+       regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask,
+                          lpc_port << snpwadr_shift);
+       if (model_data->has_hicrb_ensnp)
+               regmap_update_bits(lpc_snoop->regmap, HICRB,
+                               hicrb_en, hicrb_en);
+
+       return rc;
+}
+
+static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
+                                    int channel)
+{
+       switch (channel) {
+       case 0:
+               regmap_update_bits(lpc_snoop->regmap, HICR5,
+                                  HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
+                                  0);
+               break;
+       case 1:
+               regmap_update_bits(lpc_snoop->regmap, HICR5,
+                                  HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
+                                  0);
+               break;
+       default:
+               return;
+       }
+
+       kfifo_free(&lpc_snoop->chan[channel].fifo);
+       misc_deregister(&lpc_snoop->chan[channel].miscdev);
+}
+
+static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
+{
+       struct aspeed_lpc_snoop *lpc_snoop;
+       struct device *dev;
+       u32 port;
+       int rc;
+
+       dev = &pdev->dev;
+
+       lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL);
+       if (!lpc_snoop)
+               return -ENOMEM;
+
+       lpc_snoop->regmap = syscon_node_to_regmap(
+                       pdev->dev.parent->of_node);
+       if (IS_ERR(lpc_snoop->regmap)) {
+               dev_err(dev, "Couldn't get regmap\n");
+               return -ENODEV;
+       }
+
+       dev_set_drvdata(&pdev->dev, lpc_snoop);
+
+       rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port);
+       if (rc) {
+               dev_err(dev, "no snoop ports configured\n");
+               return -ENODEV;
+       }
+
+       rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
+       if (rc)
+               return rc;
+
+       rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port);
+       if (rc)
+               return rc;
+
+       /* Configuration of 2nd snoop channel port is optional */
+       if (of_property_read_u32_index(dev->of_node, "snoop-ports",
+                                      1, &port) == 0) {
+               rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port);
+               if (rc)
+                       aspeed_lpc_disable_snoop(lpc_snoop, 0);
+       }
+
+       return rc;
+}
+
+static int aspeed_lpc_snoop_remove(struct platform_device *pdev)
+{
+       struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
+
+       /* Disable both snoop channels */
+       aspeed_lpc_disable_snoop(lpc_snoop, 0);
+       aspeed_lpc_disable_snoop(lpc_snoop, 1);
+
+       return 0;
+}
+
+static const struct aspeed_lpc_snoop_model_data ast2400_model_data = {
+       .has_hicrb_ensnp = 0,
+};
+
+static const struct aspeed_lpc_snoop_model_data ast2500_model_data = {
+       .has_hicrb_ensnp = 1,
+};
+
+static const struct of_device_id aspeed_lpc_snoop_match[] = {
+       { .compatible = "aspeed,ast2400-lpc-snoop",
+         .data = &ast2400_model_data },
+       { .compatible = "aspeed,ast2500-lpc-snoop",
+         .data = &ast2500_model_data },
+       { },
+};
+
+static struct platform_driver aspeed_lpc_snoop_driver = {
+       .driver = {
+               .name           = DEVICE_NAME,
+               .of_match_table = aspeed_lpc_snoop_match,
+       },
+       .probe = aspeed_lpc_snoop_probe,
+       .remove = aspeed_lpc_snoop_remove,
+};
+
+module_platform_driver(aspeed_lpc_snoop_driver);
+
+MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
+MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");