12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2524feb79SPatrick Venture /* 3524feb79SPatrick Venture * Copyright 2017 Google Inc 4524feb79SPatrick Venture * 5524feb79SPatrick Venture * Provides a simple driver to control the ASPEED LPC snoop interface which 6524feb79SPatrick Venture * allows the BMC to listen on and save the data written by 7524feb79SPatrick Venture * the host to an arbitrary LPC I/O port. 8524feb79SPatrick Venture * 9524feb79SPatrick Venture * Typically used by the BMC to "watch" host boot progress via port 10524feb79SPatrick Venture * 0x80 writes made by the BIOS during the boot process. 11524feb79SPatrick Venture */ 12524feb79SPatrick Venture 13524feb79SPatrick Venture #include <linux/bitops.h> 14*3f94cf15SJae Hyun Yoo #include <linux/clk.h> 15524feb79SPatrick Venture #include <linux/interrupt.h> 16524feb79SPatrick Venture #include <linux/fs.h> 17524feb79SPatrick Venture #include <linux/kfifo.h> 18524feb79SPatrick Venture #include <linux/mfd/syscon.h> 19524feb79SPatrick Venture #include <linux/miscdevice.h> 20524feb79SPatrick Venture #include <linux/module.h> 21524feb79SPatrick Venture #include <linux/of.h> 22524feb79SPatrick Venture #include <linux/of_device.h> 23524feb79SPatrick Venture #include <linux/platform_device.h> 24524feb79SPatrick Venture #include <linux/poll.h> 25524feb79SPatrick Venture #include <linux/regmap.h> 26524feb79SPatrick Venture 27524feb79SPatrick Venture #define DEVICE_NAME "aspeed-lpc-snoop" 28524feb79SPatrick Venture 29524feb79SPatrick Venture #define NUM_SNOOP_CHANNELS 2 30524feb79SPatrick Venture #define SNOOP_FIFO_SIZE 2048 31524feb79SPatrick Venture 32524feb79SPatrick Venture #define HICR5 0x0 33524feb79SPatrick Venture #define HICR5_EN_SNP0W BIT(0) 34524feb79SPatrick Venture #define HICR5_ENINT_SNP0W BIT(1) 35524feb79SPatrick Venture #define HICR5_EN_SNP1W BIT(2) 36524feb79SPatrick Venture #define HICR5_ENINT_SNP1W BIT(3) 37524feb79SPatrick Venture 38524feb79SPatrick Venture #define HICR6 0x4 39524feb79SPatrick Venture #define HICR6_STR_SNP0W BIT(0) 40524feb79SPatrick Venture #define HICR6_STR_SNP1W BIT(1) 41524feb79SPatrick Venture #define SNPWADR 0x10 42524feb79SPatrick Venture #define SNPWADR_CH0_MASK GENMASK(15, 0) 43524feb79SPatrick Venture #define SNPWADR_CH0_SHIFT 0 44524feb79SPatrick Venture #define SNPWADR_CH1_MASK GENMASK(31, 16) 45524feb79SPatrick Venture #define SNPWADR_CH1_SHIFT 16 46524feb79SPatrick Venture #define SNPWDR 0x14 47524feb79SPatrick Venture #define SNPWDR_CH0_MASK GENMASK(7, 0) 48524feb79SPatrick Venture #define SNPWDR_CH0_SHIFT 0 49524feb79SPatrick Venture #define SNPWDR_CH1_MASK GENMASK(15, 8) 50524feb79SPatrick Venture #define SNPWDR_CH1_SHIFT 8 51524feb79SPatrick Venture #define HICRB 0x80 52524feb79SPatrick Venture #define HICRB_ENSNP0D BIT(14) 53524feb79SPatrick Venture #define HICRB_ENSNP1D BIT(15) 54524feb79SPatrick Venture 55524feb79SPatrick Venture struct aspeed_lpc_snoop_model_data { 56524feb79SPatrick Venture /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500 57524feb79SPatrick Venture * can use them. 58524feb79SPatrick Venture */ 59524feb79SPatrick Venture unsigned int has_hicrb_ensnp; 60524feb79SPatrick Venture }; 61524feb79SPatrick Venture 62524feb79SPatrick Venture struct aspeed_lpc_snoop_channel { 63524feb79SPatrick Venture struct kfifo fifo; 64524feb79SPatrick Venture wait_queue_head_t wq; 65524feb79SPatrick Venture struct miscdevice miscdev; 66524feb79SPatrick Venture }; 67524feb79SPatrick Venture 68524feb79SPatrick Venture struct aspeed_lpc_snoop { 69524feb79SPatrick Venture struct regmap *regmap; 70524feb79SPatrick Venture int irq; 71*3f94cf15SJae Hyun Yoo struct clk *clk; 72524feb79SPatrick Venture struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; 73524feb79SPatrick Venture }; 74524feb79SPatrick Venture 75524feb79SPatrick Venture static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file) 76524feb79SPatrick Venture { 77524feb79SPatrick Venture return container_of(file->private_data, 78524feb79SPatrick Venture struct aspeed_lpc_snoop_channel, 79524feb79SPatrick Venture miscdev); 80524feb79SPatrick Venture } 81524feb79SPatrick Venture 82524feb79SPatrick Venture static ssize_t snoop_file_read(struct file *file, char __user *buffer, 83524feb79SPatrick Venture size_t count, loff_t *ppos) 84524feb79SPatrick Venture { 85524feb79SPatrick Venture struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); 86524feb79SPatrick Venture unsigned int copied; 87524feb79SPatrick Venture int ret = 0; 88524feb79SPatrick Venture 89524feb79SPatrick Venture if (kfifo_is_empty(&chan->fifo)) { 90524feb79SPatrick Venture if (file->f_flags & O_NONBLOCK) 91524feb79SPatrick Venture return -EAGAIN; 92524feb79SPatrick Venture ret = wait_event_interruptible(chan->wq, 93524feb79SPatrick Venture !kfifo_is_empty(&chan->fifo)); 94524feb79SPatrick Venture if (ret == -ERESTARTSYS) 95524feb79SPatrick Venture return -EINTR; 96524feb79SPatrick Venture } 97524feb79SPatrick Venture ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); 98524feb79SPatrick Venture 99524feb79SPatrick Venture return ret ? ret : copied; 100524feb79SPatrick Venture } 101524feb79SPatrick Venture 102a4e55ccdSLuc Van Oostenryck static __poll_t snoop_file_poll(struct file *file, 103524feb79SPatrick Venture struct poll_table_struct *pt) 104524feb79SPatrick Venture { 105524feb79SPatrick Venture struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); 106524feb79SPatrick Venture 107524feb79SPatrick Venture poll_wait(file, &chan->wq, pt); 108a4e55ccdSLuc Van Oostenryck return !kfifo_is_empty(&chan->fifo) ? EPOLLIN : 0; 109524feb79SPatrick Venture } 110524feb79SPatrick Venture 111524feb79SPatrick Venture static const struct file_operations snoop_fops = { 112524feb79SPatrick Venture .owner = THIS_MODULE, 113524feb79SPatrick Venture .read = snoop_file_read, 114524feb79SPatrick Venture .poll = snoop_file_poll, 115524feb79SPatrick Venture .llseek = noop_llseek, 116524feb79SPatrick Venture }; 117524feb79SPatrick Venture 118524feb79SPatrick Venture /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ 119524feb79SPatrick Venture static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val) 120524feb79SPatrick Venture { 121524feb79SPatrick Venture if (!kfifo_initialized(&chan->fifo)) 122524feb79SPatrick Venture return; 123524feb79SPatrick Venture if (kfifo_is_full(&chan->fifo)) 124524feb79SPatrick Venture kfifo_skip(&chan->fifo); 125524feb79SPatrick Venture kfifo_put(&chan->fifo, val); 126524feb79SPatrick Venture wake_up_interruptible(&chan->wq); 127524feb79SPatrick Venture } 128524feb79SPatrick Venture 129524feb79SPatrick Venture static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) 130524feb79SPatrick Venture { 131524feb79SPatrick Venture struct aspeed_lpc_snoop *lpc_snoop = arg; 132524feb79SPatrick Venture u32 reg, data; 133524feb79SPatrick Venture 134524feb79SPatrick Venture if (regmap_read(lpc_snoop->regmap, HICR6, ®)) 135524feb79SPatrick Venture return IRQ_NONE; 136524feb79SPatrick Venture 137524feb79SPatrick Venture /* Check if one of the snoop channels is interrupting */ 138524feb79SPatrick Venture reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W); 139524feb79SPatrick Venture if (!reg) 140524feb79SPatrick Venture return IRQ_NONE; 141524feb79SPatrick Venture 142524feb79SPatrick Venture /* Ack pending IRQs */ 143524feb79SPatrick Venture regmap_write(lpc_snoop->regmap, HICR6, reg); 144524feb79SPatrick Venture 145524feb79SPatrick Venture /* Read and save most recent snoop'ed data byte to FIFO */ 146524feb79SPatrick Venture regmap_read(lpc_snoop->regmap, SNPWDR, &data); 147524feb79SPatrick Venture 148524feb79SPatrick Venture if (reg & HICR6_STR_SNP0W) { 149524feb79SPatrick Venture u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; 150524feb79SPatrick Venture 151524feb79SPatrick Venture put_fifo_with_discard(&lpc_snoop->chan[0], val); 152524feb79SPatrick Venture } 153524feb79SPatrick Venture if (reg & HICR6_STR_SNP1W) { 154524feb79SPatrick Venture u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; 155524feb79SPatrick Venture 156524feb79SPatrick Venture put_fifo_with_discard(&lpc_snoop->chan[1], val); 157524feb79SPatrick Venture } 158524feb79SPatrick Venture 159524feb79SPatrick Venture return IRQ_HANDLED; 160524feb79SPatrick Venture } 161524feb79SPatrick Venture 162524feb79SPatrick Venture static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop, 163524feb79SPatrick Venture struct platform_device *pdev) 164524feb79SPatrick Venture { 165524feb79SPatrick Venture struct device *dev = &pdev->dev; 166524feb79SPatrick Venture int rc; 167524feb79SPatrick Venture 168524feb79SPatrick Venture lpc_snoop->irq = platform_get_irq(pdev, 0); 169524feb79SPatrick Venture if (!lpc_snoop->irq) 170524feb79SPatrick Venture return -ENODEV; 171524feb79SPatrick Venture 172524feb79SPatrick Venture rc = devm_request_irq(dev, lpc_snoop->irq, 173524feb79SPatrick Venture aspeed_lpc_snoop_irq, IRQF_SHARED, 174524feb79SPatrick Venture DEVICE_NAME, lpc_snoop); 175524feb79SPatrick Venture if (rc < 0) { 176524feb79SPatrick Venture dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq); 177524feb79SPatrick Venture lpc_snoop->irq = 0; 178524feb79SPatrick Venture return rc; 179524feb79SPatrick Venture } 180524feb79SPatrick Venture 181524feb79SPatrick Venture return 0; 182524feb79SPatrick Venture } 183524feb79SPatrick Venture 184524feb79SPatrick Venture static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 185524feb79SPatrick Venture struct device *dev, 186524feb79SPatrick Venture int channel, u16 lpc_port) 187524feb79SPatrick Venture { 188524feb79SPatrick Venture int rc = 0; 189524feb79SPatrick Venture u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en; 190524feb79SPatrick Venture const struct aspeed_lpc_snoop_model_data *model_data = 191524feb79SPatrick Venture of_device_get_match_data(dev); 192524feb79SPatrick Venture 193524feb79SPatrick Venture init_waitqueue_head(&lpc_snoop->chan[channel].wq); 194524feb79SPatrick Venture /* Create FIFO datastructure */ 195524feb79SPatrick Venture rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, 196524feb79SPatrick Venture SNOOP_FIFO_SIZE, GFP_KERNEL); 197524feb79SPatrick Venture if (rc) 198524feb79SPatrick Venture return rc; 199524feb79SPatrick Venture 200524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; 201524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.name = 202524feb79SPatrick Venture devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); 203524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; 204524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.parent = dev; 205524feb79SPatrick Venture rc = misc_register(&lpc_snoop->chan[channel].miscdev); 206524feb79SPatrick Venture if (rc) 207524feb79SPatrick Venture return rc; 208524feb79SPatrick Venture 209524feb79SPatrick Venture /* Enable LPC snoop channel at requested port */ 210524feb79SPatrick Venture switch (channel) { 211524feb79SPatrick Venture case 0: 212524feb79SPatrick Venture hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W; 213524feb79SPatrick Venture snpwadr_mask = SNPWADR_CH0_MASK; 214524feb79SPatrick Venture snpwadr_shift = SNPWADR_CH0_SHIFT; 215524feb79SPatrick Venture hicrb_en = HICRB_ENSNP0D; 216524feb79SPatrick Venture break; 217524feb79SPatrick Venture case 1: 218524feb79SPatrick Venture hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W; 219524feb79SPatrick Venture snpwadr_mask = SNPWADR_CH1_MASK; 220524feb79SPatrick Venture snpwadr_shift = SNPWADR_CH1_SHIFT; 221524feb79SPatrick Venture hicrb_en = HICRB_ENSNP1D; 222524feb79SPatrick Venture break; 223524feb79SPatrick Venture default: 224524feb79SPatrick Venture return -EINVAL; 225524feb79SPatrick Venture } 226524feb79SPatrick Venture 227524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); 228524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask, 229524feb79SPatrick Venture lpc_port << snpwadr_shift); 230524feb79SPatrick Venture if (model_data->has_hicrb_ensnp) 231524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICRB, 232524feb79SPatrick Venture hicrb_en, hicrb_en); 233524feb79SPatrick Venture 234524feb79SPatrick Venture return rc; 235524feb79SPatrick Venture } 236524feb79SPatrick Venture 237524feb79SPatrick Venture static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 238524feb79SPatrick Venture int channel) 239524feb79SPatrick Venture { 240524feb79SPatrick Venture switch (channel) { 241524feb79SPatrick Venture case 0: 242524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICR5, 243524feb79SPatrick Venture HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, 244524feb79SPatrick Venture 0); 245524feb79SPatrick Venture break; 246524feb79SPatrick Venture case 1: 247524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICR5, 248524feb79SPatrick Venture HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, 249524feb79SPatrick Venture 0); 250524feb79SPatrick Venture break; 251524feb79SPatrick Venture default: 252524feb79SPatrick Venture return; 253524feb79SPatrick Venture } 254524feb79SPatrick Venture 255524feb79SPatrick Venture kfifo_free(&lpc_snoop->chan[channel].fifo); 256524feb79SPatrick Venture misc_deregister(&lpc_snoop->chan[channel].miscdev); 257524feb79SPatrick Venture } 258524feb79SPatrick Venture 259524feb79SPatrick Venture static int aspeed_lpc_snoop_probe(struct platform_device *pdev) 260524feb79SPatrick Venture { 261524feb79SPatrick Venture struct aspeed_lpc_snoop *lpc_snoop; 262524feb79SPatrick Venture struct device *dev; 263524feb79SPatrick Venture u32 port; 264524feb79SPatrick Venture int rc; 265524feb79SPatrick Venture 266524feb79SPatrick Venture dev = &pdev->dev; 267524feb79SPatrick Venture 268524feb79SPatrick Venture lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL); 269524feb79SPatrick Venture if (!lpc_snoop) 270524feb79SPatrick Venture return -ENOMEM; 271524feb79SPatrick Venture 272524feb79SPatrick Venture lpc_snoop->regmap = syscon_node_to_regmap( 273524feb79SPatrick Venture pdev->dev.parent->of_node); 274524feb79SPatrick Venture if (IS_ERR(lpc_snoop->regmap)) { 275524feb79SPatrick Venture dev_err(dev, "Couldn't get regmap\n"); 276524feb79SPatrick Venture return -ENODEV; 277524feb79SPatrick Venture } 278524feb79SPatrick Venture 279524feb79SPatrick Venture dev_set_drvdata(&pdev->dev, lpc_snoop); 280524feb79SPatrick Venture 281524feb79SPatrick Venture rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); 282524feb79SPatrick Venture if (rc) { 283524feb79SPatrick Venture dev_err(dev, "no snoop ports configured\n"); 284524feb79SPatrick Venture return -ENODEV; 285524feb79SPatrick Venture } 286524feb79SPatrick Venture 287*3f94cf15SJae Hyun Yoo lpc_snoop->clk = devm_clk_get(dev, NULL); 288*3f94cf15SJae Hyun Yoo if (IS_ERR(lpc_snoop->clk)) { 289*3f94cf15SJae Hyun Yoo rc = PTR_ERR(lpc_snoop->clk); 290*3f94cf15SJae Hyun Yoo if (rc != -EPROBE_DEFER) 291*3f94cf15SJae Hyun Yoo dev_err(dev, "couldn't get clock\n"); 292*3f94cf15SJae Hyun Yoo return rc; 293*3f94cf15SJae Hyun Yoo } 294*3f94cf15SJae Hyun Yoo rc = clk_prepare_enable(lpc_snoop->clk); 295*3f94cf15SJae Hyun Yoo if (rc) { 296*3f94cf15SJae Hyun Yoo dev_err(dev, "couldn't enable clock\n"); 297*3f94cf15SJae Hyun Yoo return rc; 298*3f94cf15SJae Hyun Yoo } 299*3f94cf15SJae Hyun Yoo 300524feb79SPatrick Venture rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); 301524feb79SPatrick Venture if (rc) 302*3f94cf15SJae Hyun Yoo goto err; 303524feb79SPatrick Venture 304524feb79SPatrick Venture rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port); 305524feb79SPatrick Venture if (rc) 306*3f94cf15SJae Hyun Yoo goto err; 307524feb79SPatrick Venture 308524feb79SPatrick Venture /* Configuration of 2nd snoop channel port is optional */ 309524feb79SPatrick Venture if (of_property_read_u32_index(dev->of_node, "snoop-ports", 310524feb79SPatrick Venture 1, &port) == 0) { 311524feb79SPatrick Venture rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port); 312*3f94cf15SJae Hyun Yoo if (rc) { 313524feb79SPatrick Venture aspeed_lpc_disable_snoop(lpc_snoop, 0); 314*3f94cf15SJae Hyun Yoo goto err; 315524feb79SPatrick Venture } 316*3f94cf15SJae Hyun Yoo } 317*3f94cf15SJae Hyun Yoo 318*3f94cf15SJae Hyun Yoo return 0; 319*3f94cf15SJae Hyun Yoo 320*3f94cf15SJae Hyun Yoo err: 321*3f94cf15SJae Hyun Yoo clk_disable_unprepare(lpc_snoop->clk); 322524feb79SPatrick Venture 323524feb79SPatrick Venture return rc; 324524feb79SPatrick Venture } 325524feb79SPatrick Venture 326524feb79SPatrick Venture static int aspeed_lpc_snoop_remove(struct platform_device *pdev) 327524feb79SPatrick Venture { 328524feb79SPatrick Venture struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); 329524feb79SPatrick Venture 330524feb79SPatrick Venture /* Disable both snoop channels */ 331524feb79SPatrick Venture aspeed_lpc_disable_snoop(lpc_snoop, 0); 332524feb79SPatrick Venture aspeed_lpc_disable_snoop(lpc_snoop, 1); 333524feb79SPatrick Venture 334*3f94cf15SJae Hyun Yoo clk_disable_unprepare(lpc_snoop->clk); 335*3f94cf15SJae Hyun Yoo 336524feb79SPatrick Venture return 0; 337524feb79SPatrick Venture } 338524feb79SPatrick Venture 339524feb79SPatrick Venture static const struct aspeed_lpc_snoop_model_data ast2400_model_data = { 340524feb79SPatrick Venture .has_hicrb_ensnp = 0, 341524feb79SPatrick Venture }; 342524feb79SPatrick Venture 343524feb79SPatrick Venture static const struct aspeed_lpc_snoop_model_data ast2500_model_data = { 344524feb79SPatrick Venture .has_hicrb_ensnp = 1, 345524feb79SPatrick Venture }; 346524feb79SPatrick Venture 347524feb79SPatrick Venture static const struct of_device_id aspeed_lpc_snoop_match[] = { 348524feb79SPatrick Venture { .compatible = "aspeed,ast2400-lpc-snoop", 349524feb79SPatrick Venture .data = &ast2400_model_data }, 350524feb79SPatrick Venture { .compatible = "aspeed,ast2500-lpc-snoop", 351524feb79SPatrick Venture .data = &ast2500_model_data }, 35244ddc4deSBrad Bishop { .compatible = "aspeed,ast2600-lpc-snoop", 35344ddc4deSBrad Bishop .data = &ast2500_model_data }, 354524feb79SPatrick Venture { }, 355524feb79SPatrick Venture }; 356524feb79SPatrick Venture 357524feb79SPatrick Venture static struct platform_driver aspeed_lpc_snoop_driver = { 358524feb79SPatrick Venture .driver = { 359524feb79SPatrick Venture .name = DEVICE_NAME, 360524feb79SPatrick Venture .of_match_table = aspeed_lpc_snoop_match, 361524feb79SPatrick Venture }, 362524feb79SPatrick Venture .probe = aspeed_lpc_snoop_probe, 363524feb79SPatrick Venture .remove = aspeed_lpc_snoop_remove, 364524feb79SPatrick Venture }; 365524feb79SPatrick Venture 366524feb79SPatrick Venture module_platform_driver(aspeed_lpc_snoop_driver); 367524feb79SPatrick Venture 368524feb79SPatrick Venture MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); 369524feb79SPatrick Venture MODULE_LICENSE("GPL"); 370524feb79SPatrick Venture MODULE_AUTHOR("Robert Lippert <rlippert@google.com>"); 371524feb79SPatrick Venture MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality"); 372