|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
| 2 | +/* |
| 3 | + * Loongson-2K Board Management Controller (BMC) Core Driver. |
| 4 | + * |
| 5 | + * Copyright (C) 2024-2025 Loongson Technology Corporation Limited. |
| 6 | + * |
| 7 | + * Authors: |
| 8 | + * Chong Qiao <qiaochong@loongson.cn> |
| 9 | + * Binbin Zhou <zhoubinbin@loongson.cn> |
| 10 | + */ |
| 11 | + |
| 12 | +#include <linux/aperture.h> |
| 13 | +#include <linux/errno.h> |
| 14 | +#include <linux/init.h> |
| 15 | +#include <linux/kernel.h> |
| 16 | +#include <linux/mfd/core.h> |
| 17 | +#include <linux/module.h> |
| 18 | +#include <linux/pci.h> |
| 19 | +#include <linux/pci_ids.h> |
| 20 | +#include <linux/platform_data/simplefb.h> |
| 21 | +#include <linux/platform_device.h> |
| 22 | + |
| 23 | +/* LS2K BMC resources */ |
| 24 | +#define LS2K_DISPLAY_RES_START (SZ_16M + SZ_2M) |
| 25 | +#define LS2K_IPMI_RES_SIZE 0x1C |
| 26 | +#define LS2K_IPMI0_RES_START (SZ_16M + 0xF00000) |
| 27 | +#define LS2K_IPMI1_RES_START (LS2K_IPMI0_RES_START + LS2K_IPMI_RES_SIZE) |
| 28 | +#define LS2K_IPMI2_RES_START (LS2K_IPMI1_RES_START + LS2K_IPMI_RES_SIZE) |
| 29 | +#define LS2K_IPMI3_RES_START (LS2K_IPMI2_RES_START + LS2K_IPMI_RES_SIZE) |
| 30 | +#define LS2K_IPMI4_RES_START (LS2K_IPMI3_RES_START + LS2K_IPMI_RES_SIZE) |
| 31 | + |
| 32 | +enum { |
| 33 | + LS2K_BMC_DISPLAY, |
| 34 | + LS2K_BMC_IPMI0, |
| 35 | + LS2K_BMC_IPMI1, |
| 36 | + LS2K_BMC_IPMI2, |
| 37 | + LS2K_BMC_IPMI3, |
| 38 | + LS2K_BMC_IPMI4, |
| 39 | +}; |
| 40 | + |
| 41 | +static struct resource ls2k_display_resources[] = { |
| 42 | + DEFINE_RES_MEM_NAMED(LS2K_DISPLAY_RES_START, SZ_4M, "simpledrm-res"), |
| 43 | +}; |
| 44 | + |
| 45 | +static struct resource ls2k_ipmi0_resources[] = { |
| 46 | + DEFINE_RES_MEM_NAMED(LS2K_IPMI0_RES_START, LS2K_IPMI_RES_SIZE, "ipmi0-res"), |
| 47 | +}; |
| 48 | + |
| 49 | +static struct resource ls2k_ipmi1_resources[] = { |
| 50 | + DEFINE_RES_MEM_NAMED(LS2K_IPMI1_RES_START, LS2K_IPMI_RES_SIZE, "ipmi1-res"), |
| 51 | +}; |
| 52 | + |
| 53 | +static struct resource ls2k_ipmi2_resources[] = { |
| 54 | + DEFINE_RES_MEM_NAMED(LS2K_IPMI2_RES_START, LS2K_IPMI_RES_SIZE, "ipmi2-res"), |
| 55 | +}; |
| 56 | + |
| 57 | +static struct resource ls2k_ipmi3_resources[] = { |
| 58 | + DEFINE_RES_MEM_NAMED(LS2K_IPMI3_RES_START, LS2K_IPMI_RES_SIZE, "ipmi3-res"), |
| 59 | +}; |
| 60 | + |
| 61 | +static struct resource ls2k_ipmi4_resources[] = { |
| 62 | + DEFINE_RES_MEM_NAMED(LS2K_IPMI4_RES_START, LS2K_IPMI_RES_SIZE, "ipmi4-res"), |
| 63 | +}; |
| 64 | + |
| 65 | +static struct mfd_cell ls2k_bmc_cells[] = { |
| 66 | + [LS2K_BMC_DISPLAY] = { |
| 67 | + .name = "simple-framebuffer", |
| 68 | + .num_resources = ARRAY_SIZE(ls2k_display_resources), |
| 69 | + .resources = ls2k_display_resources |
| 70 | + }, |
| 71 | + [LS2K_BMC_IPMI0] = { |
| 72 | + .name = "ls2k-ipmi-si", |
| 73 | + .num_resources = ARRAY_SIZE(ls2k_ipmi0_resources), |
| 74 | + .resources = ls2k_ipmi0_resources |
| 75 | + }, |
| 76 | + [LS2K_BMC_IPMI1] = { |
| 77 | + .name = "ls2k-ipmi-si", |
| 78 | + .num_resources = ARRAY_SIZE(ls2k_ipmi1_resources), |
| 79 | + .resources = ls2k_ipmi1_resources |
| 80 | + }, |
| 81 | + [LS2K_BMC_IPMI2] = { |
| 82 | + .name = "ls2k-ipmi-si", |
| 83 | + .num_resources = ARRAY_SIZE(ls2k_ipmi2_resources), |
| 84 | + .resources = ls2k_ipmi2_resources |
| 85 | + }, |
| 86 | + [LS2K_BMC_IPMI3] = { |
| 87 | + .name = "ls2k-ipmi-si", |
| 88 | + .num_resources = ARRAY_SIZE(ls2k_ipmi3_resources), |
| 89 | + .resources = ls2k_ipmi3_resources |
| 90 | + }, |
| 91 | + [LS2K_BMC_IPMI4] = { |
| 92 | + .name = "ls2k-ipmi-si", |
| 93 | + .num_resources = ARRAY_SIZE(ls2k_ipmi4_resources), |
| 94 | + .resources = ls2k_ipmi4_resources |
| 95 | + }, |
| 96 | +}; |
| 97 | + |
| 98 | +/* |
| 99 | + * Currently the Loongson-2K BMC hardware does not have an I2C interface to adapt to the |
| 100 | + * resolution. We set the resolution by presetting "video=1280x1024-16@2M" to the BMC memory. |
| 101 | + */ |
| 102 | +static int ls2k_bmc_parse_mode(struct pci_dev *pdev, struct simplefb_platform_data *pd) |
| 103 | +{ |
| 104 | + char *mode; |
| 105 | + int depth, ret; |
| 106 | + |
| 107 | + /* The last 16M of PCI BAR0 is used to store the resolution string. */ |
| 108 | + mode = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 0) + SZ_16M, SZ_16M); |
| 109 | + if (!mode) |
| 110 | + return -ENOMEM; |
| 111 | + |
| 112 | + /* The resolution field starts with the flag "video=". */ |
| 113 | + if (!strncmp(mode, "video=", 6)) |
| 114 | + mode = mode + 6; |
| 115 | + |
| 116 | + ret = kstrtoint(strsep(&mode, "x"), 10, &pd->width); |
| 117 | + if (ret) |
| 118 | + return ret; |
| 119 | + |
| 120 | + ret = kstrtoint(strsep(&mode, "-"), 10, &pd->height); |
| 121 | + if (ret) |
| 122 | + return ret; |
| 123 | + |
| 124 | + ret = kstrtoint(strsep(&mode, "@"), 10, &depth); |
| 125 | + if (ret) |
| 126 | + return ret; |
| 127 | + |
| 128 | + pd->stride = pd->width * depth / 8; |
| 129 | + pd->format = depth == 32 ? "a8r8g8b8" : "r5g6b5"; |
| 130 | + |
| 131 | + return 0; |
| 132 | +} |
| 133 | + |
| 134 | +static int ls2k_bmc_probe(struct pci_dev *dev, const struct pci_device_id *id) |
| 135 | +{ |
| 136 | + struct simplefb_platform_data pd; |
| 137 | + resource_size_t base; |
| 138 | + int ret; |
| 139 | + |
| 140 | + ret = pci_enable_device(dev); |
| 141 | + if (ret) |
| 142 | + return ret; |
| 143 | + |
| 144 | + ret = ls2k_bmc_parse_mode(dev, &pd); |
| 145 | + if (ret) |
| 146 | + goto disable_pci; |
| 147 | + |
| 148 | + ls2k_bmc_cells[LS2K_BMC_DISPLAY].platform_data = &pd; |
| 149 | + ls2k_bmc_cells[LS2K_BMC_DISPLAY].pdata_size = sizeof(pd); |
| 150 | + base = dev->resource[0].start + LS2K_DISPLAY_RES_START; |
| 151 | + |
| 152 | + /* Remove conflicting efifb device */ |
| 153 | + ret = aperture_remove_conflicting_devices(base, SZ_4M, "simple-framebuffer"); |
| 154 | + if (ret) { |
| 155 | + dev_err(&dev->dev, "Failed to removed firmware framebuffers: %d\n", ret); |
| 156 | + goto disable_pci; |
| 157 | + } |
| 158 | + |
| 159 | + return devm_mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, |
| 160 | + ls2k_bmc_cells, ARRAY_SIZE(ls2k_bmc_cells), |
| 161 | + &dev->resource[0], 0, NULL); |
| 162 | + |
| 163 | +disable_pci: |
| 164 | + pci_disable_device(dev); |
| 165 | + return ret; |
| 166 | +} |
| 167 | + |
| 168 | +static void ls2k_bmc_remove(struct pci_dev *dev) |
| 169 | +{ |
| 170 | + pci_disable_device(dev); |
| 171 | +} |
| 172 | + |
| 173 | +static struct pci_device_id ls2k_bmc_devices[] = { |
| 174 | + { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x1a05) }, |
| 175 | + { } |
| 176 | +}; |
| 177 | +MODULE_DEVICE_TABLE(pci, ls2k_bmc_devices); |
| 178 | + |
| 179 | +static struct pci_driver ls2k_bmc_driver = { |
| 180 | + .name = "ls2k-bmc", |
| 181 | + .id_table = ls2k_bmc_devices, |
| 182 | + .probe = ls2k_bmc_probe, |
| 183 | + .remove = ls2k_bmc_remove, |
| 184 | +}; |
| 185 | +module_pci_driver(ls2k_bmc_driver); |
| 186 | + |
| 187 | +MODULE_DESCRIPTION("Loongson-2K Board Management Controller (BMC) Core driver"); |
| 188 | +MODULE_AUTHOR("Loongson Technology Corporation Limited"); |
| 189 | +MODULE_LICENSE("GPL"); |
0 commit comments