18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2017 Google Inc 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Provides a simple driver to control the ASPEED LPC snoop interface which 68c2ecf20Sopenharmony_ci * allows the BMC to listen on and save the data written by 78c2ecf20Sopenharmony_ci * the host to an arbitrary LPC I/O port. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Typically used by the BMC to "watch" host boot progress via port 108c2ecf20Sopenharmony_ci * 0x80 writes made by the BIOS during the boot process. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/bitops.h> 148c2ecf20Sopenharmony_ci#include <linux/clk.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci#include <linux/fs.h> 178c2ecf20Sopenharmony_ci#include <linux/kfifo.h> 188c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 198c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/of.h> 228c2ecf20Sopenharmony_ci#include <linux/of_device.h> 238c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 248c2ecf20Sopenharmony_ci#include <linux/poll.h> 258c2ecf20Sopenharmony_ci#include <linux/regmap.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define DEVICE_NAME "aspeed-lpc-snoop" 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define NUM_SNOOP_CHANNELS 2 308c2ecf20Sopenharmony_ci#define SNOOP_FIFO_SIZE 2048 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define HICR5 0x0 338c2ecf20Sopenharmony_ci#define HICR5_EN_SNP0W BIT(0) 348c2ecf20Sopenharmony_ci#define HICR5_ENINT_SNP0W BIT(1) 358c2ecf20Sopenharmony_ci#define HICR5_EN_SNP1W BIT(2) 368c2ecf20Sopenharmony_ci#define HICR5_ENINT_SNP1W BIT(3) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define HICR6 0x4 398c2ecf20Sopenharmony_ci#define HICR6_STR_SNP0W BIT(0) 408c2ecf20Sopenharmony_ci#define HICR6_STR_SNP1W BIT(1) 418c2ecf20Sopenharmony_ci#define SNPWADR 0x10 428c2ecf20Sopenharmony_ci#define SNPWADR_CH0_MASK GENMASK(15, 0) 438c2ecf20Sopenharmony_ci#define SNPWADR_CH0_SHIFT 0 448c2ecf20Sopenharmony_ci#define SNPWADR_CH1_MASK GENMASK(31, 16) 458c2ecf20Sopenharmony_ci#define SNPWADR_CH1_SHIFT 16 468c2ecf20Sopenharmony_ci#define SNPWDR 0x14 478c2ecf20Sopenharmony_ci#define SNPWDR_CH0_MASK GENMASK(7, 0) 488c2ecf20Sopenharmony_ci#define SNPWDR_CH0_SHIFT 0 498c2ecf20Sopenharmony_ci#define SNPWDR_CH1_MASK GENMASK(15, 8) 508c2ecf20Sopenharmony_ci#define SNPWDR_CH1_SHIFT 8 518c2ecf20Sopenharmony_ci#define HICRB 0x80 528c2ecf20Sopenharmony_ci#define HICRB_ENSNP0D BIT(14) 538c2ecf20Sopenharmony_ci#define HICRB_ENSNP1D BIT(15) 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistruct aspeed_lpc_snoop_model_data { 568c2ecf20Sopenharmony_ci /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500 578c2ecf20Sopenharmony_ci * can use them. 588c2ecf20Sopenharmony_ci */ 598c2ecf20Sopenharmony_ci unsigned int has_hicrb_ensnp; 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct aspeed_lpc_snoop_channel { 638c2ecf20Sopenharmony_ci struct kfifo fifo; 648c2ecf20Sopenharmony_ci wait_queue_head_t wq; 658c2ecf20Sopenharmony_ci struct miscdevice miscdev; 668c2ecf20Sopenharmony_ci}; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistruct aspeed_lpc_snoop { 698c2ecf20Sopenharmony_ci struct regmap *regmap; 708c2ecf20Sopenharmony_ci int irq; 718c2ecf20Sopenharmony_ci struct clk *clk; 728c2ecf20Sopenharmony_ci struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; 738c2ecf20Sopenharmony_ci}; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci return container_of(file->private_data, 788c2ecf20Sopenharmony_ci struct aspeed_lpc_snoop_channel, 798c2ecf20Sopenharmony_ci miscdev); 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic ssize_t snoop_file_read(struct file *file, char __user *buffer, 838c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); 868c2ecf20Sopenharmony_ci unsigned int copied; 878c2ecf20Sopenharmony_ci int ret = 0; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (kfifo_is_empty(&chan->fifo)) { 908c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 918c2ecf20Sopenharmony_ci return -EAGAIN; 928c2ecf20Sopenharmony_ci ret = wait_event_interruptible(chan->wq, 938c2ecf20Sopenharmony_ci !kfifo_is_empty(&chan->fifo)); 948c2ecf20Sopenharmony_ci if (ret == -ERESTARTSYS) 958c2ecf20Sopenharmony_ci return -EINTR; 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); 988c2ecf20Sopenharmony_ci if (ret) 998c2ecf20Sopenharmony_ci return ret; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return copied; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic __poll_t snoop_file_poll(struct file *file, 1058c2ecf20Sopenharmony_ci struct poll_table_struct *pt) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci poll_wait(file, &chan->wq, pt); 1108c2ecf20Sopenharmony_ci return !kfifo_is_empty(&chan->fifo) ? EPOLLIN : 0; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic const struct file_operations snoop_fops = { 1148c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1158c2ecf20Sopenharmony_ci .read = snoop_file_read, 1168c2ecf20Sopenharmony_ci .poll = snoop_file_poll, 1178c2ecf20Sopenharmony_ci .llseek = noop_llseek, 1188c2ecf20Sopenharmony_ci}; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ 1218c2ecf20Sopenharmony_cistatic void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci if (!kfifo_initialized(&chan->fifo)) 1248c2ecf20Sopenharmony_ci return; 1258c2ecf20Sopenharmony_ci if (kfifo_is_full(&chan->fifo)) 1268c2ecf20Sopenharmony_ci kfifo_skip(&chan->fifo); 1278c2ecf20Sopenharmony_ci kfifo_put(&chan->fifo, val); 1288c2ecf20Sopenharmony_ci wake_up_interruptible(&chan->wq); 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct aspeed_lpc_snoop *lpc_snoop = arg; 1348c2ecf20Sopenharmony_ci u32 reg, data; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (regmap_read(lpc_snoop->regmap, HICR6, ®)) 1378c2ecf20Sopenharmony_ci return IRQ_NONE; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* Check if one of the snoop channels is interrupting */ 1408c2ecf20Sopenharmony_ci reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W); 1418c2ecf20Sopenharmony_ci if (!reg) 1428c2ecf20Sopenharmony_ci return IRQ_NONE; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* Ack pending IRQs */ 1458c2ecf20Sopenharmony_ci regmap_write(lpc_snoop->regmap, HICR6, reg); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* Read and save most recent snoop'ed data byte to FIFO */ 1488c2ecf20Sopenharmony_ci regmap_read(lpc_snoop->regmap, SNPWDR, &data); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci if (reg & HICR6_STR_SNP0W) { 1518c2ecf20Sopenharmony_ci u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci put_fifo_with_discard(&lpc_snoop->chan[0], val); 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci if (reg & HICR6_STR_SNP1W) { 1568c2ecf20Sopenharmony_ci u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci put_fifo_with_discard(&lpc_snoop->chan[1], val); 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop, 1658c2ecf20Sopenharmony_ci struct platform_device *pdev) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1688c2ecf20Sopenharmony_ci int rc; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci lpc_snoop->irq = platform_get_irq(pdev, 0); 1718c2ecf20Sopenharmony_ci if (!lpc_snoop->irq) 1728c2ecf20Sopenharmony_ci return -ENODEV; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci rc = devm_request_irq(dev, lpc_snoop->irq, 1758c2ecf20Sopenharmony_ci aspeed_lpc_snoop_irq, IRQF_SHARED, 1768c2ecf20Sopenharmony_ci DEVICE_NAME, lpc_snoop); 1778c2ecf20Sopenharmony_ci if (rc < 0) { 1788c2ecf20Sopenharmony_ci dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq); 1798c2ecf20Sopenharmony_ci lpc_snoop->irq = 0; 1808c2ecf20Sopenharmony_ci return rc; 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci return 0; 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 1878c2ecf20Sopenharmony_ci struct device *dev, 1888c2ecf20Sopenharmony_ci int channel, u16 lpc_port) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci int rc = 0; 1918c2ecf20Sopenharmony_ci u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en; 1928c2ecf20Sopenharmony_ci const struct aspeed_lpc_snoop_model_data *model_data = 1938c2ecf20Sopenharmony_ci of_device_get_match_data(dev); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci init_waitqueue_head(&lpc_snoop->chan[channel].wq); 1968c2ecf20Sopenharmony_ci /* Create FIFO datastructure */ 1978c2ecf20Sopenharmony_ci rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, 1988c2ecf20Sopenharmony_ci SNOOP_FIFO_SIZE, GFP_KERNEL); 1998c2ecf20Sopenharmony_ci if (rc) 2008c2ecf20Sopenharmony_ci return rc; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; 2038c2ecf20Sopenharmony_ci lpc_snoop->chan[channel].miscdev.name = 2048c2ecf20Sopenharmony_ci devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); 2058c2ecf20Sopenharmony_ci lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; 2068c2ecf20Sopenharmony_ci lpc_snoop->chan[channel].miscdev.parent = dev; 2078c2ecf20Sopenharmony_ci rc = misc_register(&lpc_snoop->chan[channel].miscdev); 2088c2ecf20Sopenharmony_ci if (rc) 2098c2ecf20Sopenharmony_ci return rc; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* Enable LPC snoop channel at requested port */ 2128c2ecf20Sopenharmony_ci switch (channel) { 2138c2ecf20Sopenharmony_ci case 0: 2148c2ecf20Sopenharmony_ci hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W; 2158c2ecf20Sopenharmony_ci snpwadr_mask = SNPWADR_CH0_MASK; 2168c2ecf20Sopenharmony_ci snpwadr_shift = SNPWADR_CH0_SHIFT; 2178c2ecf20Sopenharmony_ci hicrb_en = HICRB_ENSNP0D; 2188c2ecf20Sopenharmony_ci break; 2198c2ecf20Sopenharmony_ci case 1: 2208c2ecf20Sopenharmony_ci hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W; 2218c2ecf20Sopenharmony_ci snpwadr_mask = SNPWADR_CH1_MASK; 2228c2ecf20Sopenharmony_ci snpwadr_shift = SNPWADR_CH1_SHIFT; 2238c2ecf20Sopenharmony_ci hicrb_en = HICRB_ENSNP1D; 2248c2ecf20Sopenharmony_ci break; 2258c2ecf20Sopenharmony_ci default: 2268c2ecf20Sopenharmony_ci return -EINVAL; 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); 2308c2ecf20Sopenharmony_ci regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask, 2318c2ecf20Sopenharmony_ci lpc_port << snpwadr_shift); 2328c2ecf20Sopenharmony_ci if (model_data->has_hicrb_ensnp) 2338c2ecf20Sopenharmony_ci regmap_update_bits(lpc_snoop->regmap, HICRB, 2348c2ecf20Sopenharmony_ci hicrb_en, hicrb_en); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return rc; 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 2408c2ecf20Sopenharmony_ci int channel) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci switch (channel) { 2438c2ecf20Sopenharmony_ci case 0: 2448c2ecf20Sopenharmony_ci regmap_update_bits(lpc_snoop->regmap, HICR5, 2458c2ecf20Sopenharmony_ci HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, 2468c2ecf20Sopenharmony_ci 0); 2478c2ecf20Sopenharmony_ci break; 2488c2ecf20Sopenharmony_ci case 1: 2498c2ecf20Sopenharmony_ci regmap_update_bits(lpc_snoop->regmap, HICR5, 2508c2ecf20Sopenharmony_ci HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, 2518c2ecf20Sopenharmony_ci 0); 2528c2ecf20Sopenharmony_ci break; 2538c2ecf20Sopenharmony_ci default: 2548c2ecf20Sopenharmony_ci return; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci kfifo_free(&lpc_snoop->chan[channel].fifo); 2588c2ecf20Sopenharmony_ci misc_deregister(&lpc_snoop->chan[channel].miscdev); 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic int aspeed_lpc_snoop_probe(struct platform_device *pdev) 2628c2ecf20Sopenharmony_ci{ 2638c2ecf20Sopenharmony_ci struct aspeed_lpc_snoop *lpc_snoop; 2648c2ecf20Sopenharmony_ci struct device *dev; 2658c2ecf20Sopenharmony_ci u32 port; 2668c2ecf20Sopenharmony_ci int rc; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci dev = &pdev->dev; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL); 2718c2ecf20Sopenharmony_ci if (!lpc_snoop) 2728c2ecf20Sopenharmony_ci return -ENOMEM; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci lpc_snoop->regmap = syscon_node_to_regmap( 2758c2ecf20Sopenharmony_ci pdev->dev.parent->of_node); 2768c2ecf20Sopenharmony_ci if (IS_ERR(lpc_snoop->regmap)) { 2778c2ecf20Sopenharmony_ci dev_err(dev, "Couldn't get regmap\n"); 2788c2ecf20Sopenharmony_ci return -ENODEV; 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci dev_set_drvdata(&pdev->dev, lpc_snoop); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); 2848c2ecf20Sopenharmony_ci if (rc) { 2858c2ecf20Sopenharmony_ci dev_err(dev, "no snoop ports configured\n"); 2868c2ecf20Sopenharmony_ci return -ENODEV; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci lpc_snoop->clk = devm_clk_get(dev, NULL); 2908c2ecf20Sopenharmony_ci if (IS_ERR(lpc_snoop->clk)) { 2918c2ecf20Sopenharmony_ci rc = PTR_ERR(lpc_snoop->clk); 2928c2ecf20Sopenharmony_ci if (rc != -EPROBE_DEFER) 2938c2ecf20Sopenharmony_ci dev_err(dev, "couldn't get clock\n"); 2948c2ecf20Sopenharmony_ci return rc; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci rc = clk_prepare_enable(lpc_snoop->clk); 2978c2ecf20Sopenharmony_ci if (rc) { 2988c2ecf20Sopenharmony_ci dev_err(dev, "couldn't enable clock\n"); 2998c2ecf20Sopenharmony_ci return rc; 3008c2ecf20Sopenharmony_ci } 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); 3038c2ecf20Sopenharmony_ci if (rc) 3048c2ecf20Sopenharmony_ci goto err; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port); 3078c2ecf20Sopenharmony_ci if (rc) 3088c2ecf20Sopenharmony_ci goto err; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci /* Configuration of 2nd snoop channel port is optional */ 3118c2ecf20Sopenharmony_ci if (of_property_read_u32_index(dev->of_node, "snoop-ports", 3128c2ecf20Sopenharmony_ci 1, &port) == 0) { 3138c2ecf20Sopenharmony_ci rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port); 3148c2ecf20Sopenharmony_ci if (rc) { 3158c2ecf20Sopenharmony_ci aspeed_lpc_disable_snoop(lpc_snoop, 0); 3168c2ecf20Sopenharmony_ci goto err; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci } 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci return 0; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_cierr: 3238c2ecf20Sopenharmony_ci clk_disable_unprepare(lpc_snoop->clk); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci return rc; 3268c2ecf20Sopenharmony_ci} 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_cistatic int aspeed_lpc_snoop_remove(struct platform_device *pdev) 3298c2ecf20Sopenharmony_ci{ 3308c2ecf20Sopenharmony_ci struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci /* Disable both snoop channels */ 3338c2ecf20Sopenharmony_ci aspeed_lpc_disable_snoop(lpc_snoop, 0); 3348c2ecf20Sopenharmony_ci aspeed_lpc_disable_snoop(lpc_snoop, 1); 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci clk_disable_unprepare(lpc_snoop->clk); 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci return 0; 3398c2ecf20Sopenharmony_ci} 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_cistatic const struct aspeed_lpc_snoop_model_data ast2400_model_data = { 3428c2ecf20Sopenharmony_ci .has_hicrb_ensnp = 0, 3438c2ecf20Sopenharmony_ci}; 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_cistatic const struct aspeed_lpc_snoop_model_data ast2500_model_data = { 3468c2ecf20Sopenharmony_ci .has_hicrb_ensnp = 1, 3478c2ecf20Sopenharmony_ci}; 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_cistatic const struct of_device_id aspeed_lpc_snoop_match[] = { 3508c2ecf20Sopenharmony_ci { .compatible = "aspeed,ast2400-lpc-snoop", 3518c2ecf20Sopenharmony_ci .data = &ast2400_model_data }, 3528c2ecf20Sopenharmony_ci { .compatible = "aspeed,ast2500-lpc-snoop", 3538c2ecf20Sopenharmony_ci .data = &ast2500_model_data }, 3548c2ecf20Sopenharmony_ci { }, 3558c2ecf20Sopenharmony_ci}; 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_cistatic struct platform_driver aspeed_lpc_snoop_driver = { 3588c2ecf20Sopenharmony_ci .driver = { 3598c2ecf20Sopenharmony_ci .name = DEVICE_NAME, 3608c2ecf20Sopenharmony_ci .of_match_table = aspeed_lpc_snoop_match, 3618c2ecf20Sopenharmony_ci }, 3628c2ecf20Sopenharmony_ci .probe = aspeed_lpc_snoop_probe, 3638c2ecf20Sopenharmony_ci .remove = aspeed_lpc_snoop_remove, 3648c2ecf20Sopenharmony_ci}; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_cimodule_platform_driver(aspeed_lpc_snoop_driver); 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); 3698c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3708c2ecf20Sopenharmony_ciMODULE_AUTHOR("Robert Lippert <rlippert@google.com>"); 3718c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality"); 372