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> 14524feb79SPatrick Venture #include <linux/interrupt.h> 15524feb79SPatrick Venture #include <linux/fs.h> 16524feb79SPatrick Venture #include <linux/kfifo.h> 17524feb79SPatrick Venture #include <linux/mfd/syscon.h> 18524feb79SPatrick Venture #include <linux/miscdevice.h> 19524feb79SPatrick Venture #include <linux/module.h> 20524feb79SPatrick Venture #include <linux/of.h> 21524feb79SPatrick Venture #include <linux/of_device.h> 22524feb79SPatrick Venture #include <linux/platform_device.h> 23524feb79SPatrick Venture #include <linux/poll.h> 24524feb79SPatrick Venture #include <linux/regmap.h> 25524feb79SPatrick Venture 26524feb79SPatrick Venture #define DEVICE_NAME "aspeed-lpc-snoop" 27524feb79SPatrick Venture 28524feb79SPatrick Venture #define NUM_SNOOP_CHANNELS 2 29524feb79SPatrick Venture #define SNOOP_FIFO_SIZE 2048 30524feb79SPatrick Venture 31524feb79SPatrick Venture #define HICR5 0x0 32524feb79SPatrick Venture #define HICR5_EN_SNP0W BIT(0) 33524feb79SPatrick Venture #define HICR5_ENINT_SNP0W BIT(1) 34524feb79SPatrick Venture #define HICR5_EN_SNP1W BIT(2) 35524feb79SPatrick Venture #define HICR5_ENINT_SNP1W BIT(3) 36524feb79SPatrick Venture 37524feb79SPatrick Venture #define HICR6 0x4 38524feb79SPatrick Venture #define HICR6_STR_SNP0W BIT(0) 39524feb79SPatrick Venture #define HICR6_STR_SNP1W BIT(1) 40524feb79SPatrick Venture #define SNPWADR 0x10 41524feb79SPatrick Venture #define SNPWADR_CH0_MASK GENMASK(15, 0) 42524feb79SPatrick Venture #define SNPWADR_CH0_SHIFT 0 43524feb79SPatrick Venture #define SNPWADR_CH1_MASK GENMASK(31, 16) 44524feb79SPatrick Venture #define SNPWADR_CH1_SHIFT 16 45524feb79SPatrick Venture #define SNPWDR 0x14 46524feb79SPatrick Venture #define SNPWDR_CH0_MASK GENMASK(7, 0) 47524feb79SPatrick Venture #define SNPWDR_CH0_SHIFT 0 48524feb79SPatrick Venture #define SNPWDR_CH1_MASK GENMASK(15, 8) 49524feb79SPatrick Venture #define SNPWDR_CH1_SHIFT 8 50524feb79SPatrick Venture #define HICRB 0x80 51524feb79SPatrick Venture #define HICRB_ENSNP0D BIT(14) 52524feb79SPatrick Venture #define HICRB_ENSNP1D BIT(15) 53524feb79SPatrick Venture 54524feb79SPatrick Venture struct aspeed_lpc_snoop_model_data { 55524feb79SPatrick Venture /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500 56524feb79SPatrick Venture * can use them. 57524feb79SPatrick Venture */ 58524feb79SPatrick Venture unsigned int has_hicrb_ensnp; 59524feb79SPatrick Venture }; 60524feb79SPatrick Venture 61524feb79SPatrick Venture struct aspeed_lpc_snoop_channel { 62524feb79SPatrick Venture struct kfifo fifo; 63524feb79SPatrick Venture wait_queue_head_t wq; 64524feb79SPatrick Venture struct miscdevice miscdev; 65524feb79SPatrick Venture }; 66524feb79SPatrick Venture 67524feb79SPatrick Venture struct aspeed_lpc_snoop { 68524feb79SPatrick Venture struct regmap *regmap; 69524feb79SPatrick Venture int irq; 70524feb79SPatrick Venture struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; 71524feb79SPatrick Venture }; 72524feb79SPatrick Venture 73524feb79SPatrick Venture static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file) 74524feb79SPatrick Venture { 75524feb79SPatrick Venture return container_of(file->private_data, 76524feb79SPatrick Venture struct aspeed_lpc_snoop_channel, 77524feb79SPatrick Venture miscdev); 78524feb79SPatrick Venture } 79524feb79SPatrick Venture 80524feb79SPatrick Venture static ssize_t snoop_file_read(struct file *file, char __user *buffer, 81524feb79SPatrick Venture size_t count, loff_t *ppos) 82524feb79SPatrick Venture { 83524feb79SPatrick Venture struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); 84524feb79SPatrick Venture unsigned int copied; 85524feb79SPatrick Venture int ret = 0; 86524feb79SPatrick Venture 87524feb79SPatrick Venture if (kfifo_is_empty(&chan->fifo)) { 88524feb79SPatrick Venture if (file->f_flags & O_NONBLOCK) 89524feb79SPatrick Venture return -EAGAIN; 90524feb79SPatrick Venture ret = wait_event_interruptible(chan->wq, 91524feb79SPatrick Venture !kfifo_is_empty(&chan->fifo)); 92524feb79SPatrick Venture if (ret == -ERESTARTSYS) 93524feb79SPatrick Venture return -EINTR; 94524feb79SPatrick Venture } 95524feb79SPatrick Venture ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); 96524feb79SPatrick Venture 97524feb79SPatrick Venture return ret ? ret : copied; 98524feb79SPatrick Venture } 99524feb79SPatrick Venture 100524feb79SPatrick Venture static unsigned int snoop_file_poll(struct file *file, 101524feb79SPatrick Venture struct poll_table_struct *pt) 102524feb79SPatrick Venture { 103524feb79SPatrick Venture struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); 104524feb79SPatrick Venture 105524feb79SPatrick Venture poll_wait(file, &chan->wq, pt); 106524feb79SPatrick Venture return !kfifo_is_empty(&chan->fifo) ? POLLIN : 0; 107524feb79SPatrick Venture } 108524feb79SPatrick Venture 109524feb79SPatrick Venture static const struct file_operations snoop_fops = { 110524feb79SPatrick Venture .owner = THIS_MODULE, 111524feb79SPatrick Venture .read = snoop_file_read, 112524feb79SPatrick Venture .poll = snoop_file_poll, 113524feb79SPatrick Venture .llseek = noop_llseek, 114524feb79SPatrick Venture }; 115524feb79SPatrick Venture 116524feb79SPatrick Venture /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ 117524feb79SPatrick Venture static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val) 118524feb79SPatrick Venture { 119524feb79SPatrick Venture if (!kfifo_initialized(&chan->fifo)) 120524feb79SPatrick Venture return; 121524feb79SPatrick Venture if (kfifo_is_full(&chan->fifo)) 122524feb79SPatrick Venture kfifo_skip(&chan->fifo); 123524feb79SPatrick Venture kfifo_put(&chan->fifo, val); 124524feb79SPatrick Venture wake_up_interruptible(&chan->wq); 125524feb79SPatrick Venture } 126524feb79SPatrick Venture 127524feb79SPatrick Venture static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) 128524feb79SPatrick Venture { 129524feb79SPatrick Venture struct aspeed_lpc_snoop *lpc_snoop = arg; 130524feb79SPatrick Venture u32 reg, data; 131524feb79SPatrick Venture 132524feb79SPatrick Venture if (regmap_read(lpc_snoop->regmap, HICR6, ®)) 133524feb79SPatrick Venture return IRQ_NONE; 134524feb79SPatrick Venture 135524feb79SPatrick Venture /* Check if one of the snoop channels is interrupting */ 136524feb79SPatrick Venture reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W); 137524feb79SPatrick Venture if (!reg) 138524feb79SPatrick Venture return IRQ_NONE; 139524feb79SPatrick Venture 140524feb79SPatrick Venture /* Ack pending IRQs */ 141524feb79SPatrick Venture regmap_write(lpc_snoop->regmap, HICR6, reg); 142524feb79SPatrick Venture 143524feb79SPatrick Venture /* Read and save most recent snoop'ed data byte to FIFO */ 144524feb79SPatrick Venture regmap_read(lpc_snoop->regmap, SNPWDR, &data); 145524feb79SPatrick Venture 146524feb79SPatrick Venture if (reg & HICR6_STR_SNP0W) { 147524feb79SPatrick Venture u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; 148524feb79SPatrick Venture 149524feb79SPatrick Venture put_fifo_with_discard(&lpc_snoop->chan[0], val); 150524feb79SPatrick Venture } 151524feb79SPatrick Venture if (reg & HICR6_STR_SNP1W) { 152524feb79SPatrick Venture u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; 153524feb79SPatrick Venture 154524feb79SPatrick Venture put_fifo_with_discard(&lpc_snoop->chan[1], val); 155524feb79SPatrick Venture } 156524feb79SPatrick Venture 157524feb79SPatrick Venture return IRQ_HANDLED; 158524feb79SPatrick Venture } 159524feb79SPatrick Venture 160524feb79SPatrick Venture static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop, 161524feb79SPatrick Venture struct platform_device *pdev) 162524feb79SPatrick Venture { 163524feb79SPatrick Venture struct device *dev = &pdev->dev; 164524feb79SPatrick Venture int rc; 165524feb79SPatrick Venture 166524feb79SPatrick Venture lpc_snoop->irq = platform_get_irq(pdev, 0); 167524feb79SPatrick Venture if (!lpc_snoop->irq) 168524feb79SPatrick Venture return -ENODEV; 169524feb79SPatrick Venture 170524feb79SPatrick Venture rc = devm_request_irq(dev, lpc_snoop->irq, 171524feb79SPatrick Venture aspeed_lpc_snoop_irq, IRQF_SHARED, 172524feb79SPatrick Venture DEVICE_NAME, lpc_snoop); 173524feb79SPatrick Venture if (rc < 0) { 174524feb79SPatrick Venture dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq); 175524feb79SPatrick Venture lpc_snoop->irq = 0; 176524feb79SPatrick Venture return rc; 177524feb79SPatrick Venture } 178524feb79SPatrick Venture 179524feb79SPatrick Venture return 0; 180524feb79SPatrick Venture } 181524feb79SPatrick Venture 182524feb79SPatrick Venture static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 183524feb79SPatrick Venture struct device *dev, 184524feb79SPatrick Venture int channel, u16 lpc_port) 185524feb79SPatrick Venture { 186524feb79SPatrick Venture int rc = 0; 187524feb79SPatrick Venture u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en; 188524feb79SPatrick Venture const struct aspeed_lpc_snoop_model_data *model_data = 189524feb79SPatrick Venture of_device_get_match_data(dev); 190524feb79SPatrick Venture 191524feb79SPatrick Venture init_waitqueue_head(&lpc_snoop->chan[channel].wq); 192524feb79SPatrick Venture /* Create FIFO datastructure */ 193524feb79SPatrick Venture rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, 194524feb79SPatrick Venture SNOOP_FIFO_SIZE, GFP_KERNEL); 195524feb79SPatrick Venture if (rc) 196524feb79SPatrick Venture return rc; 197524feb79SPatrick Venture 198524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; 199524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.name = 200524feb79SPatrick Venture devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); 201524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; 202524feb79SPatrick Venture lpc_snoop->chan[channel].miscdev.parent = dev; 203524feb79SPatrick Venture rc = misc_register(&lpc_snoop->chan[channel].miscdev); 204524feb79SPatrick Venture if (rc) 205524feb79SPatrick Venture return rc; 206524feb79SPatrick Venture 207524feb79SPatrick Venture /* Enable LPC snoop channel at requested port */ 208524feb79SPatrick Venture switch (channel) { 209524feb79SPatrick Venture case 0: 210524feb79SPatrick Venture hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W; 211524feb79SPatrick Venture snpwadr_mask = SNPWADR_CH0_MASK; 212524feb79SPatrick Venture snpwadr_shift = SNPWADR_CH0_SHIFT; 213524feb79SPatrick Venture hicrb_en = HICRB_ENSNP0D; 214524feb79SPatrick Venture break; 215524feb79SPatrick Venture case 1: 216524feb79SPatrick Venture hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W; 217524feb79SPatrick Venture snpwadr_mask = SNPWADR_CH1_MASK; 218524feb79SPatrick Venture snpwadr_shift = SNPWADR_CH1_SHIFT; 219524feb79SPatrick Venture hicrb_en = HICRB_ENSNP1D; 220524feb79SPatrick Venture break; 221524feb79SPatrick Venture default: 222524feb79SPatrick Venture return -EINVAL; 223524feb79SPatrick Venture } 224524feb79SPatrick Venture 225524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); 226524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask, 227524feb79SPatrick Venture lpc_port << snpwadr_shift); 228524feb79SPatrick Venture if (model_data->has_hicrb_ensnp) 229524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICRB, 230524feb79SPatrick Venture hicrb_en, hicrb_en); 231524feb79SPatrick Venture 232524feb79SPatrick Venture return rc; 233524feb79SPatrick Venture } 234524feb79SPatrick Venture 235524feb79SPatrick Venture static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 236524feb79SPatrick Venture int channel) 237524feb79SPatrick Venture { 238524feb79SPatrick Venture switch (channel) { 239524feb79SPatrick Venture case 0: 240524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICR5, 241524feb79SPatrick Venture HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, 242524feb79SPatrick Venture 0); 243524feb79SPatrick Venture break; 244524feb79SPatrick Venture case 1: 245524feb79SPatrick Venture regmap_update_bits(lpc_snoop->regmap, HICR5, 246524feb79SPatrick Venture HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, 247524feb79SPatrick Venture 0); 248524feb79SPatrick Venture break; 249524feb79SPatrick Venture default: 250524feb79SPatrick Venture return; 251524feb79SPatrick Venture } 252524feb79SPatrick Venture 253524feb79SPatrick Venture kfifo_free(&lpc_snoop->chan[channel].fifo); 254524feb79SPatrick Venture misc_deregister(&lpc_snoop->chan[channel].miscdev); 255524feb79SPatrick Venture } 256524feb79SPatrick Venture 257524feb79SPatrick Venture static int aspeed_lpc_snoop_probe(struct platform_device *pdev) 258524feb79SPatrick Venture { 259524feb79SPatrick Venture struct aspeed_lpc_snoop *lpc_snoop; 260524feb79SPatrick Venture struct device *dev; 261524feb79SPatrick Venture u32 port; 262524feb79SPatrick Venture int rc; 263524feb79SPatrick Venture 264524feb79SPatrick Venture dev = &pdev->dev; 265524feb79SPatrick Venture 266524feb79SPatrick Venture lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL); 267524feb79SPatrick Venture if (!lpc_snoop) 268524feb79SPatrick Venture return -ENOMEM; 269524feb79SPatrick Venture 270524feb79SPatrick Venture lpc_snoop->regmap = syscon_node_to_regmap( 271524feb79SPatrick Venture pdev->dev.parent->of_node); 272524feb79SPatrick Venture if (IS_ERR(lpc_snoop->regmap)) { 273524feb79SPatrick Venture dev_err(dev, "Couldn't get regmap\n"); 274524feb79SPatrick Venture return -ENODEV; 275524feb79SPatrick Venture } 276524feb79SPatrick Venture 277524feb79SPatrick Venture dev_set_drvdata(&pdev->dev, lpc_snoop); 278524feb79SPatrick Venture 279524feb79SPatrick Venture rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); 280524feb79SPatrick Venture if (rc) { 281524feb79SPatrick Venture dev_err(dev, "no snoop ports configured\n"); 282524feb79SPatrick Venture return -ENODEV; 283524feb79SPatrick Venture } 284524feb79SPatrick Venture 285524feb79SPatrick Venture rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); 286524feb79SPatrick Venture if (rc) 287524feb79SPatrick Venture return rc; 288524feb79SPatrick Venture 289524feb79SPatrick Venture rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port); 290524feb79SPatrick Venture if (rc) 291524feb79SPatrick Venture return rc; 292524feb79SPatrick Venture 293524feb79SPatrick Venture /* Configuration of 2nd snoop channel port is optional */ 294524feb79SPatrick Venture if (of_property_read_u32_index(dev->of_node, "snoop-ports", 295524feb79SPatrick Venture 1, &port) == 0) { 296524feb79SPatrick Venture rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port); 297524feb79SPatrick Venture if (rc) 298524feb79SPatrick Venture aspeed_lpc_disable_snoop(lpc_snoop, 0); 299524feb79SPatrick Venture } 300524feb79SPatrick Venture 301524feb79SPatrick Venture return rc; 302524feb79SPatrick Venture } 303524feb79SPatrick Venture 304524feb79SPatrick Venture static int aspeed_lpc_snoop_remove(struct platform_device *pdev) 305524feb79SPatrick Venture { 306524feb79SPatrick Venture struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); 307524feb79SPatrick Venture 308524feb79SPatrick Venture /* Disable both snoop channels */ 309524feb79SPatrick Venture aspeed_lpc_disable_snoop(lpc_snoop, 0); 310524feb79SPatrick Venture aspeed_lpc_disable_snoop(lpc_snoop, 1); 311524feb79SPatrick Venture 312524feb79SPatrick Venture return 0; 313524feb79SPatrick Venture } 314524feb79SPatrick Venture 315524feb79SPatrick Venture static const struct aspeed_lpc_snoop_model_data ast2400_model_data = { 316524feb79SPatrick Venture .has_hicrb_ensnp = 0, 317524feb79SPatrick Venture }; 318524feb79SPatrick Venture 319524feb79SPatrick Venture static const struct aspeed_lpc_snoop_model_data ast2500_model_data = { 320524feb79SPatrick Venture .has_hicrb_ensnp = 1, 321524feb79SPatrick Venture }; 322524feb79SPatrick Venture 323524feb79SPatrick Venture static const struct of_device_id aspeed_lpc_snoop_match[] = { 324524feb79SPatrick Venture { .compatible = "aspeed,ast2400-lpc-snoop", 325524feb79SPatrick Venture .data = &ast2400_model_data }, 326524feb79SPatrick Venture { .compatible = "aspeed,ast2500-lpc-snoop", 327524feb79SPatrick Venture .data = &ast2500_model_data }, 328524feb79SPatrick Venture { }, 329524feb79SPatrick Venture }; 330524feb79SPatrick Venture 331524feb79SPatrick Venture static struct platform_driver aspeed_lpc_snoop_driver = { 332524feb79SPatrick Venture .driver = { 333524feb79SPatrick Venture .name = DEVICE_NAME, 334524feb79SPatrick Venture .of_match_table = aspeed_lpc_snoop_match, 335524feb79SPatrick Venture }, 336524feb79SPatrick Venture .probe = aspeed_lpc_snoop_probe, 337524feb79SPatrick Venture .remove = aspeed_lpc_snoop_remove, 338524feb79SPatrick Venture }; 339524feb79SPatrick Venture 340524feb79SPatrick Venture module_platform_driver(aspeed_lpc_snoop_driver); 341524feb79SPatrick Venture 342524feb79SPatrick Venture MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); 343524feb79SPatrick Venture MODULE_LICENSE("GPL"); 344524feb79SPatrick Venture MODULE_AUTHOR("Robert Lippert <rlippert@google.com>"); 345524feb79SPatrick Venture MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality"); 346