1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * MEN Chameleon Bus. 4 * 5 * Copyright (C) 2014 MEN Mikroelektronik GmbH (www.men.de) 6 * Author: Andreas Werner <andreas.werner@men.de> 7 */ 8 9#include <linux/platform_device.h> 10#include <linux/module.h> 11#include <linux/dmi.h> 12#include <linux/mcb.h> 13#include <linux/io.h> 14#include "mcb-internal.h" 15 16struct priv { 17 struct mcb_bus *bus; 18 struct resource *mem; 19 void __iomem *base; 20}; 21 22static int mcb_lpc_probe(struct platform_device *pdev) 23{ 24 struct resource *res; 25 struct priv *priv; 26 int ret = 0, table_size; 27 28 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 29 if (!priv) 30 return -ENOMEM; 31 32 priv->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 33 if (!priv->mem) { 34 dev_err(&pdev->dev, "No Memory resource\n"); 35 return -ENODEV; 36 } 37 38 res = devm_request_mem_region(&pdev->dev, priv->mem->start, 39 resource_size(priv->mem), 40 KBUILD_MODNAME); 41 if (!res) { 42 dev_err(&pdev->dev, "Failed to request IO memory\n"); 43 return -EBUSY; 44 } 45 46 priv->base = devm_ioremap(&pdev->dev, priv->mem->start, 47 resource_size(priv->mem)); 48 if (!priv->base) { 49 dev_err(&pdev->dev, "Cannot ioremap\n"); 50 return -ENOMEM; 51 } 52 53 platform_set_drvdata(pdev, priv); 54 55 priv->bus = mcb_alloc_bus(&pdev->dev); 56 if (IS_ERR(priv->bus)) 57 return PTR_ERR(priv->bus); 58 59 ret = chameleon_parse_cells(priv->bus, priv->mem->start, priv->base); 60 if (ret < 0) { 61 goto out_mcb_bus; 62 } 63 64 table_size = ret; 65 66 if (table_size < CHAM_HEADER_SIZE) { 67 /* Release the previous resources */ 68 devm_iounmap(&pdev->dev, priv->base); 69 devm_release_mem_region(&pdev->dev, priv->mem->start, resource_size(priv->mem)); 70 71 /* Then, allocate it again with the actual chameleon table size */ 72 res = devm_request_mem_region(&pdev->dev, priv->mem->start, 73 table_size, 74 KBUILD_MODNAME); 75 if (!res) { 76 dev_err(&pdev->dev, "Failed to request PCI memory\n"); 77 ret = -EBUSY; 78 goto out_mcb_bus; 79 } 80 81 priv->base = devm_ioremap(&pdev->dev, priv->mem->start, table_size); 82 if (!priv->base) { 83 dev_err(&pdev->dev, "Cannot ioremap\n"); 84 ret = -ENOMEM; 85 goto out_mcb_bus; 86 } 87 88 platform_set_drvdata(pdev, priv); 89 } 90 91 mcb_bus_add_devices(priv->bus); 92 93 return 0; 94 95out_mcb_bus: 96 mcb_release_bus(priv->bus); 97 return ret; 98} 99 100static int mcb_lpc_remove(struct platform_device *pdev) 101{ 102 struct priv *priv = platform_get_drvdata(pdev); 103 104 mcb_release_bus(priv->bus); 105 106 return 0; 107} 108 109static struct platform_device *mcb_lpc_pdev; 110 111static int mcb_lpc_create_platform_device(const struct dmi_system_id *id) 112{ 113 struct resource *res = id->driver_data; 114 int ret; 115 116 mcb_lpc_pdev = platform_device_alloc("mcb-lpc", -1); 117 if (!mcb_lpc_pdev) 118 return -ENOMEM; 119 120 ret = platform_device_add_resources(mcb_lpc_pdev, res, 1); 121 if (ret) 122 goto out_put; 123 124 ret = platform_device_add(mcb_lpc_pdev); 125 if (ret) 126 goto out_put; 127 128 return 0; 129 130out_put: 131 platform_device_put(mcb_lpc_pdev); 132 return ret; 133} 134 135static struct resource sc24_fpga_resource = { 136 .start = 0xe000e000, 137 .end = 0xe000e000 + CHAM_HEADER_SIZE, 138 .flags = IORESOURCE_MEM, 139}; 140 141static struct resource sc31_fpga_resource = { 142 .start = 0xf000e000, 143 .end = 0xf000e000 + CHAM_HEADER_SIZE, 144 .flags = IORESOURCE_MEM, 145}; 146 147static struct platform_driver mcb_lpc_driver = { 148 .driver = { 149 .name = "mcb-lpc", 150 }, 151 .probe = mcb_lpc_probe, 152 .remove = mcb_lpc_remove, 153}; 154 155static const struct dmi_system_id mcb_lpc_dmi_table[] = { 156 { 157 .ident = "SC24", 158 .matches = { 159 DMI_MATCH(DMI_SYS_VENDOR, "MEN"), 160 DMI_MATCH(DMI_PRODUCT_VERSION, "14SC24"), 161 }, 162 .driver_data = (void *)&sc24_fpga_resource, 163 .callback = mcb_lpc_create_platform_device, 164 }, 165 { 166 .ident = "SC31", 167 .matches = { 168 DMI_MATCH(DMI_SYS_VENDOR, "MEN"), 169 DMI_MATCH(DMI_PRODUCT_VERSION, "14SC31"), 170 }, 171 .driver_data = (void *)&sc31_fpga_resource, 172 .callback = mcb_lpc_create_platform_device, 173 }, 174 {} 175}; 176MODULE_DEVICE_TABLE(dmi, mcb_lpc_dmi_table); 177 178static int __init mcb_lpc_init(void) 179{ 180 if (!dmi_check_system(mcb_lpc_dmi_table)) 181 return -ENODEV; 182 183 return platform_driver_register(&mcb_lpc_driver); 184} 185 186static void __exit mcb_lpc_exit(void) 187{ 188 platform_device_unregister(mcb_lpc_pdev); 189 platform_driver_unregister(&mcb_lpc_driver); 190} 191 192module_init(mcb_lpc_init); 193module_exit(mcb_lpc_exit); 194 195MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); 196MODULE_LICENSE("GPL"); 197MODULE_DESCRIPTION("MCB over LPC support"); 198MODULE_IMPORT_NS(MCB); 199