linux/drivers/soc/aspeed/aspeed-lpc-snoop.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright 2017 Google Inc
   4 *
   5 * Provides a simple driver to control the ASPEED LPC snoop interface which
   6 * allows the BMC to listen on and save the data written by
   7 * the host to an arbitrary LPC I/O port.
   8 *
   9 * Typically used by the BMC to "watch" host boot progress via port
  10 * 0x80 writes made by the BIOS during the boot process.
  11 */
  12
  13#include <linux/bitops.h>
  14#include <linux/clk.h>
  15#include <linux/interrupt.h>
  16#include <linux/fs.h>
  17#include <linux/kfifo.h>
  18#include <linux/mfd/syscon.h>
  19#include <linux/miscdevice.h>
  20#include <linux/module.h>
  21#include <linux/of.h>
  22#include <linux/of_device.h>
  23#include <linux/platform_device.h>
  24#include <linux/poll.h>
  25#include <linux/regmap.h>
  26
  27#define DEVICE_NAME     "aspeed-lpc-snoop"
  28
  29#define NUM_SNOOP_CHANNELS 2
  30#define SNOOP_FIFO_SIZE 2048
  31
  32#define HICR5   0x80
  33#define HICR5_EN_SNP0W          BIT(0)
  34#define HICR5_ENINT_SNP0W       BIT(1)
  35#define HICR5_EN_SNP1W          BIT(2)
  36#define HICR5_ENINT_SNP1W       BIT(3)
  37#define HICR6   0x84
  38#define HICR6_STR_SNP0W         BIT(0)
  39#define HICR6_STR_SNP1W         BIT(1)
  40#define SNPWADR 0x90
  41#define SNPWADR_CH0_MASK        GENMASK(15, 0)
  42#define SNPWADR_CH0_SHIFT       0
  43#define SNPWADR_CH1_MASK        GENMASK(31, 16)
  44#define SNPWADR_CH1_SHIFT       16
  45#define SNPWDR  0x94
  46#define SNPWDR_CH0_MASK         GENMASK(7, 0)
  47#define SNPWDR_CH0_SHIFT        0
  48#define SNPWDR_CH1_MASK         GENMASK(15, 8)
  49#define SNPWDR_CH1_SHIFT        8
  50#define HICRB   0x100
  51#define HICRB_ENSNP0D           BIT(14)
  52#define HICRB_ENSNP1D           BIT(15)
  53
  54struct aspeed_lpc_snoop_model_data {
  55        /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500
  56         * can use them.
  57         */
  58        unsigned int has_hicrb_ensnp;
  59};
  60
  61struct aspeed_lpc_snoop_channel {
  62        struct kfifo            fifo;
  63        wait_queue_head_t       wq;
  64        struct miscdevice       miscdev;
  65};
  66
  67struct aspeed_lpc_snoop {
  68        struct regmap           *regmap;
  69        int                     irq;
  70        struct clk              *clk;
  71        struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS];
  72};
  73
  74static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file)
  75{
  76        return container_of(file->private_data,
  77                            struct aspeed_lpc_snoop_channel,
  78                            miscdev);
  79}
  80
  81static ssize_t snoop_file_read(struct file *file, char __user *buffer,
  82                                size_t count, loff_t *ppos)
  83{
  84        struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file);
  85        unsigned int copied;
  86        int ret = 0;
  87
  88        if (kfifo_is_empty(&chan->fifo)) {
  89                if (file->f_flags & O_NONBLOCK)
  90                        return -EAGAIN;
  91                ret = wait_event_interruptible(chan->wq,
  92                                !kfifo_is_empty(&chan->fifo));
  93                if (ret == -ERESTARTSYS)
  94                        return -EINTR;
  95        }
  96        ret = kfifo_to_user(&chan->fifo, buffer, count, &copied);
  97        if (ret)
  98                return ret;
  99
 100        return copied;
 101}
 102
 103static __poll_t snoop_file_poll(struct file *file,
 104                                    struct poll_table_struct *pt)
 105{
 106        struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file);
 107
 108        poll_wait(file, &chan->wq, pt);
 109        return !kfifo_is_empty(&chan->fifo) ? EPOLLIN : 0;
 110}
 111
 112static const struct file_operations snoop_fops = {
 113        .owner  = THIS_MODULE,
 114        .read   = snoop_file_read,
 115        .poll   = snoop_file_poll,
 116        .llseek = noop_llseek,
 117};
 118
 119/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
 120static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val)
 121{
 122        if (!kfifo_initialized(&chan->fifo))
 123                return;
 124        if (kfifo_is_full(&chan->fifo))
 125                kfifo_skip(&chan->fifo);
 126        kfifo_put(&chan->fifo, val);
 127        wake_up_interruptible(&chan->wq);
 128}
 129
 130static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg)
 131{
 132        struct aspeed_lpc_snoop *lpc_snoop = arg;
 133        u32 reg, data;
 134
 135        if (regmap_read(lpc_snoop->regmap, HICR6, &reg))
 136                return IRQ_NONE;
 137
 138        /* Check if one of the snoop channels is interrupting */
 139        reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W);
 140        if (!reg)
 141                return IRQ_NONE;
 142
 143        /* Ack pending IRQs */
 144        regmap_write(lpc_snoop->regmap, HICR6, reg);
 145
 146        /* Read and save most recent snoop'ed data byte to FIFO */
 147        regmap_read(lpc_snoop->regmap, SNPWDR, &data);
 148
 149        if (reg & HICR6_STR_SNP0W) {
 150                u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT;
 151
 152                put_fifo_with_discard(&lpc_snoop->chan[0], val);
 153        }
 154        if (reg & HICR6_STR_SNP1W) {
 155                u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT;
 156
 157                put_fifo_with_discard(&lpc_snoop->chan[1], val);
 158        }
 159
 160        return IRQ_HANDLED;
 161}
 162
 163static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
 164                                       struct platform_device *pdev)
 165{
 166        struct device *dev = &pdev->dev;
 167        int rc;
 168
 169        lpc_snoop->irq = platform_get_irq(pdev, 0);
 170        if (!lpc_snoop->irq)
 171                return -ENODEV;
 172
 173        rc = devm_request_irq(dev, lpc_snoop->irq,
 174                              aspeed_lpc_snoop_irq, IRQF_SHARED,
 175                              DEVICE_NAME, lpc_snoop);
 176        if (rc < 0) {
 177                dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq);
 178                lpc_snoop->irq = 0;
 179                return rc;
 180        }
 181
 182        return 0;
 183}
 184
 185static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
 186                                   struct device *dev,
 187                                   int channel, u16 lpc_port)
 188{
 189        int rc = 0;
 190        u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en;
 191        const struct aspeed_lpc_snoop_model_data *model_data =
 192                of_device_get_match_data(dev);
 193
 194        init_waitqueue_head(&lpc_snoop->chan[channel].wq);
 195        /* Create FIFO datastructure */
 196        rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo,
 197                         SNOOP_FIFO_SIZE, GFP_KERNEL);
 198        if (rc)
 199                return rc;
 200
 201        lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR;
 202        lpc_snoop->chan[channel].miscdev.name =
 203                devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel);
 204        lpc_snoop->chan[channel].miscdev.fops = &snoop_fops;
 205        lpc_snoop->chan[channel].miscdev.parent = dev;
 206        rc = misc_register(&lpc_snoop->chan[channel].miscdev);
 207        if (rc)
 208                return rc;
 209
 210        /* Enable LPC snoop channel at requested port */
 211        switch (channel) {
 212        case 0:
 213                hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W;
 214                snpwadr_mask = SNPWADR_CH0_MASK;
 215                snpwadr_shift = SNPWADR_CH0_SHIFT;
 216                hicrb_en = HICRB_ENSNP0D;
 217                break;
 218        case 1:
 219                hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W;
 220                snpwadr_mask = SNPWADR_CH1_MASK;
 221                snpwadr_shift = SNPWADR_CH1_SHIFT;
 222                hicrb_en = HICRB_ENSNP1D;
 223                break;
 224        default:
 225                return -EINVAL;
 226        }
 227
 228        regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en);
 229        regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask,
 230                           lpc_port << snpwadr_shift);
 231        if (model_data->has_hicrb_ensnp)
 232                regmap_update_bits(lpc_snoop->regmap, HICRB,
 233                                hicrb_en, hicrb_en);
 234
 235        return rc;
 236}
 237
 238static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
 239                                     int channel)
 240{
 241        switch (channel) {
 242        case 0:
 243                regmap_update_bits(lpc_snoop->regmap, HICR5,
 244                                   HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
 245                                   0);
 246                break;
 247        case 1:
 248                regmap_update_bits(lpc_snoop->regmap, HICR5,
 249                                   HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
 250                                   0);
 251                break;
 252        default:
 253                return;
 254        }
 255
 256        kfifo_free(&lpc_snoop->chan[channel].fifo);
 257        misc_deregister(&lpc_snoop->chan[channel].miscdev);
 258}
 259
 260static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
 261{
 262        struct aspeed_lpc_snoop *lpc_snoop;
 263        struct device *dev;
 264        struct device_node *np;
 265        u32 port;
 266        int rc;
 267
 268        dev = &pdev->dev;
 269
 270        lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL);
 271        if (!lpc_snoop)
 272                return -ENOMEM;
 273
 274        np = pdev->dev.parent->of_node;
 275        if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") &&
 276            !of_device_is_compatible(np, "aspeed,ast2500-lpc-v2") &&
 277            !of_device_is_compatible(np, "aspeed,ast2600-lpc-v2")) {
 278                dev_err(dev, "unsupported LPC device binding\n");
 279                return -ENODEV;
 280        }
 281
 282        lpc_snoop->regmap = syscon_node_to_regmap(np);
 283        if (IS_ERR(lpc_snoop->regmap)) {
 284                dev_err(dev, "Couldn't get regmap\n");
 285                return -ENODEV;
 286        }
 287
 288        dev_set_drvdata(&pdev->dev, lpc_snoop);
 289
 290        rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port);
 291        if (rc) {
 292                dev_err(dev, "no snoop ports configured\n");
 293                return -ENODEV;
 294        }
 295
 296        lpc_snoop->clk = devm_clk_get(dev, NULL);
 297        if (IS_ERR(lpc_snoop->clk)) {
 298                rc = PTR_ERR(lpc_snoop->clk);
 299                if (rc != -EPROBE_DEFER)
 300                        dev_err(dev, "couldn't get clock\n");
 301                return rc;
 302        }
 303        rc = clk_prepare_enable(lpc_snoop->clk);
 304        if (rc) {
 305                dev_err(dev, "couldn't enable clock\n");
 306                return rc;
 307        }
 308
 309        rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
 310        if (rc)
 311                goto err;
 312
 313        rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port);
 314        if (rc)
 315                goto err;
 316
 317        /* Configuration of 2nd snoop channel port is optional */
 318        if (of_property_read_u32_index(dev->of_node, "snoop-ports",
 319                                       1, &port) == 0) {
 320                rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port);
 321                if (rc) {
 322                        aspeed_lpc_disable_snoop(lpc_snoop, 0);
 323                        goto err;
 324                }
 325        }
 326
 327        return 0;
 328
 329err:
 330        clk_disable_unprepare(lpc_snoop->clk);
 331
 332        return rc;
 333}
 334
 335static int aspeed_lpc_snoop_remove(struct platform_device *pdev)
 336{
 337        struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
 338
 339        /* Disable both snoop channels */
 340        aspeed_lpc_disable_snoop(lpc_snoop, 0);
 341        aspeed_lpc_disable_snoop(lpc_snoop, 1);
 342
 343        clk_disable_unprepare(lpc_snoop->clk);
 344
 345        return 0;
 346}
 347
 348static const struct aspeed_lpc_snoop_model_data ast2400_model_data = {
 349        .has_hicrb_ensnp = 0,
 350};
 351
 352static const struct aspeed_lpc_snoop_model_data ast2500_model_data = {
 353        .has_hicrb_ensnp = 1,
 354};
 355
 356static const struct of_device_id aspeed_lpc_snoop_match[] = {
 357        { .compatible = "aspeed,ast2400-lpc-snoop",
 358          .data = &ast2400_model_data },
 359        { .compatible = "aspeed,ast2500-lpc-snoop",
 360          .data = &ast2500_model_data },
 361        { .compatible = "aspeed,ast2600-lpc-snoop",
 362          .data = &ast2500_model_data },
 363        { },
 364};
 365
 366static struct platform_driver aspeed_lpc_snoop_driver = {
 367        .driver = {
 368                .name           = DEVICE_NAME,
 369                .of_match_table = aspeed_lpc_snoop_match,
 370        },
 371        .probe = aspeed_lpc_snoop_probe,
 372        .remove = aspeed_lpc_snoop_remove,
 373};
 374
 375module_platform_driver(aspeed_lpc_snoop_driver);
 376
 377MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match);
 378MODULE_LICENSE("GPL");
 379MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
 380MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");
 381