]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/misc/aspeed-lpc-snoop.c
Merge branch 'core-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[karo-tx-linux.git] / drivers / misc / aspeed-lpc-snoop.c
1 /*
2  * Copyright 2017 Google Inc
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version
7  * 2 of the License, or (at your option) any later version.
8  *
9  * Provides a simple driver to control the ASPEED LPC snoop interface which
10  * allows the BMC to listen on and save the data written by
11  * the host to an arbitrary LPC I/O port.
12  *
13  * Typically used by the BMC to "watch" host boot progress via port
14  * 0x80 writes made by the BIOS during the boot process.
15  */
16
17 #include <linux/bitops.h>
18 #include <linux/interrupt.h>
19 #include <linux/kfifo.h>
20 #include <linux/mfd/syscon.h>
21 #include <linux/module.h>
22 #include <linux/of.h>
23 #include <linux/platform_device.h>
24 #include <linux/regmap.h>
25
26 #define DEVICE_NAME     "aspeed-lpc-snoop"
27
28 #define NUM_SNOOP_CHANNELS 2
29 #define SNOOP_FIFO_SIZE 2048
30
31 #define HICR5   0x0
32 #define HICR5_EN_SNP0W          BIT(0)
33 #define HICR5_ENINT_SNP0W       BIT(1)
34 #define HICR5_EN_SNP1W          BIT(2)
35 #define HICR5_ENINT_SNP1W       BIT(3)
36
37 #define HICR6   0x4
38 #define HICR6_STR_SNP0W         BIT(0)
39 #define HICR6_STR_SNP1W         BIT(1)
40 #define SNPWADR 0x10
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  0x14
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   0x80
51 #define HICRB_ENSNP0D           BIT(14)
52 #define HICRB_ENSNP1D           BIT(15)
53
54 struct aspeed_lpc_snoop {
55         struct regmap           *regmap;
56         int                     irq;
57         struct kfifo            snoop_fifo[NUM_SNOOP_CHANNELS];
58 };
59
60 /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
61 static void put_fifo_with_discard(struct kfifo *fifo, u8 val)
62 {
63         if (!kfifo_initialized(fifo))
64                 return;
65         if (kfifo_is_full(fifo))
66                 kfifo_skip(fifo);
67         kfifo_put(fifo, val);
68 }
69
70 static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg)
71 {
72         struct aspeed_lpc_snoop *lpc_snoop = arg;
73         u32 reg, data;
74
75         if (regmap_read(lpc_snoop->regmap, HICR6, &reg))
76                 return IRQ_NONE;
77
78         /* Check if one of the snoop channels is interrupting */
79         reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W);
80         if (!reg)
81                 return IRQ_NONE;
82
83         /* Ack pending IRQs */
84         regmap_write(lpc_snoop->regmap, HICR6, reg);
85
86         /* Read and save most recent snoop'ed data byte to FIFO */
87         regmap_read(lpc_snoop->regmap, SNPWDR, &data);
88
89         if (reg & HICR6_STR_SNP0W) {
90                 u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT;
91
92                 put_fifo_with_discard(&lpc_snoop->snoop_fifo[0], val);
93         }
94         if (reg & HICR6_STR_SNP1W) {
95                 u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT;
96
97                 put_fifo_with_discard(&lpc_snoop->snoop_fifo[1], val);
98         }
99
100         return IRQ_HANDLED;
101 }
102
103 static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
104                                        struct platform_device *pdev)
105 {
106         struct device *dev = &pdev->dev;
107         int rc;
108
109         lpc_snoop->irq = platform_get_irq(pdev, 0);
110         if (!lpc_snoop->irq)
111                 return -ENODEV;
112
113         rc = devm_request_irq(dev, lpc_snoop->irq,
114                               aspeed_lpc_snoop_irq, IRQF_SHARED,
115                               DEVICE_NAME, lpc_snoop);
116         if (rc < 0) {
117                 dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq);
118                 lpc_snoop->irq = 0;
119                 return rc;
120         }
121
122         return 0;
123 }
124
125 static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
126                                   int channel, u16 lpc_port)
127 {
128         int rc = 0;
129         u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en;
130
131         /* Create FIFO datastructure */
132         rc = kfifo_alloc(&lpc_snoop->snoop_fifo[channel],
133                          SNOOP_FIFO_SIZE, GFP_KERNEL);
134         if (rc)
135                 return rc;
136
137         /* Enable LPC snoop channel at requested port */
138         switch (channel) {
139         case 0:
140                 hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W;
141                 snpwadr_mask = SNPWADR_CH0_MASK;
142                 snpwadr_shift = SNPWADR_CH0_SHIFT;
143                 hicrb_en = HICRB_ENSNP0D;
144                 break;
145         case 1:
146                 hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W;
147                 snpwadr_mask = SNPWADR_CH1_MASK;
148                 snpwadr_shift = SNPWADR_CH1_SHIFT;
149                 hicrb_en = HICRB_ENSNP1D;
150                 break;
151         default:
152                 return -EINVAL;
153         }
154
155         regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en);
156         regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask,
157                            lpc_port << snpwadr_shift);
158         regmap_update_bits(lpc_snoop->regmap, HICRB, hicrb_en, hicrb_en);
159
160         return rc;
161 }
162
163 static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
164                                      int channel)
165 {
166         switch (channel) {
167         case 0:
168                 regmap_update_bits(lpc_snoop->regmap, HICR5,
169                                    HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
170                                    0);
171                 break;
172         case 1:
173                 regmap_update_bits(lpc_snoop->regmap, HICR5,
174                                    HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
175                                    0);
176                 break;
177         default:
178                 return;
179         }
180
181         kfifo_free(&lpc_snoop->snoop_fifo[channel]);
182 }
183
184 static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
185 {
186         struct aspeed_lpc_snoop *lpc_snoop;
187         struct device *dev;
188         u32 port;
189         int rc;
190
191         dev = &pdev->dev;
192
193         lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL);
194         if (!lpc_snoop)
195                 return -ENOMEM;
196
197         lpc_snoop->regmap = syscon_node_to_regmap(
198                         pdev->dev.parent->of_node);
199         if (IS_ERR(lpc_snoop->regmap)) {
200                 dev_err(dev, "Couldn't get regmap\n");
201                 return -ENODEV;
202         }
203
204         dev_set_drvdata(&pdev->dev, lpc_snoop);
205
206         rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port);
207         if (rc) {
208                 dev_err(dev, "no snoop ports configured\n");
209                 return -ENODEV;
210         }
211
212         rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
213         if (rc)
214                 return rc;
215
216         rc = aspeed_lpc_enable_snoop(lpc_snoop, 0, port);
217         if (rc)
218                 return rc;
219
220         /* Configuration of 2nd snoop channel port is optional */
221         if (of_property_read_u32_index(dev->of_node, "snoop-ports",
222                                        1, &port) == 0) {
223                 rc = aspeed_lpc_enable_snoop(lpc_snoop, 1, port);
224                 if (rc)
225                         aspeed_lpc_disable_snoop(lpc_snoop, 0);
226         }
227
228         return rc;
229 }
230
231 static int aspeed_lpc_snoop_remove(struct platform_device *pdev)
232 {
233         struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
234
235         /* Disable both snoop channels */
236         aspeed_lpc_disable_snoop(lpc_snoop, 0);
237         aspeed_lpc_disable_snoop(lpc_snoop, 1);
238
239         return 0;
240 }
241
242 static const struct of_device_id aspeed_lpc_snoop_match[] = {
243         { .compatible = "aspeed,ast2500-lpc-snoop" },
244         { },
245 };
246
247 static struct platform_driver aspeed_lpc_snoop_driver = {
248         .driver = {
249                 .name           = DEVICE_NAME,
250                 .of_match_table = aspeed_lpc_snoop_match,
251         },
252         .probe = aspeed_lpc_snoop_probe,
253         .remove = aspeed_lpc_snoop_remove,
254 };
255
256 module_platform_driver(aspeed_lpc_snoop_driver);
257
258 MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match);
259 MODULE_LICENSE("GPL");
260 MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
261 MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");