mirror of https://github.com/torvalds/linux.git
533 lines
16 KiB
C
533 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Loongson-2K Board Management Controller (BMC) Core Driver.
|
|
*
|
|
* Copyright (C) 2024-2025 Loongson Technology Corporation Limited.
|
|
*
|
|
* Authors:
|
|
* Chong Qiao <qiaochong@loongson.cn>
|
|
* Binbin Zhou <zhoubinbin@loongson.cn>
|
|
*/
|
|
|
|
#include <linux/aperture.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/kbd_kern.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mfd/core.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/pci_ids.h>
|
|
#include <linux/platform_data/simplefb.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/stop_machine.h>
|
|
#include <linux/vt_kern.h>
|
|
|
|
/* LS2K BMC resources */
|
|
#define LS2K_DISPLAY_RES_START (SZ_16M + SZ_2M)
|
|
#define LS2K_IPMI_RES_SIZE 0x1C
|
|
#define LS2K_IPMI0_RES_START (SZ_16M + 0xF00000)
|
|
#define LS2K_IPMI1_RES_START (LS2K_IPMI0_RES_START + LS2K_IPMI_RES_SIZE)
|
|
#define LS2K_IPMI2_RES_START (LS2K_IPMI1_RES_START + LS2K_IPMI_RES_SIZE)
|
|
#define LS2K_IPMI3_RES_START (LS2K_IPMI2_RES_START + LS2K_IPMI_RES_SIZE)
|
|
#define LS2K_IPMI4_RES_START (LS2K_IPMI3_RES_START + LS2K_IPMI_RES_SIZE)
|
|
|
|
#define LS7A_PCI_CFG_SIZE 0x100
|
|
|
|
/* LS7A bridge registers */
|
|
#define LS7A_PCIE_PORT_CTL0 0x0
|
|
#define LS7A_PCIE_PORT_STS1 0xC
|
|
#define LS7A_GEN2_CTL 0x80C
|
|
#define LS7A_SYMBOL_TIMER 0x71C
|
|
|
|
/* Bits of LS7A_PCIE_PORT_CTL0 */
|
|
#define LS2K_BMC_PCIE_LTSSM_ENABLE BIT(3)
|
|
|
|
/* Bits of LS7A_PCIE_PORT_STS1 */
|
|
#define LS2K_BMC_PCIE_LTSSM_STS GENMASK(5, 0)
|
|
#define LS2K_BMC_PCIE_CONNECTED 0x11
|
|
|
|
#define LS2K_BMC_PCIE_DELAY_US 1000
|
|
#define LS2K_BMC_PCIE_TIMEOUT_US 1000000
|
|
|
|
/* Bits of LS7A_GEN2_CTL */
|
|
#define LS7A_GEN2_SPEED_CHANG BIT(17)
|
|
#define LS7A_CONF_PHY_TX BIT(18)
|
|
|
|
/* Bits of LS7A_SYMBOL_TIMER */
|
|
#define LS7A_MASK_LEN_MATCH BIT(26)
|
|
|
|
/* Interval between interruptions */
|
|
#define LS2K_BMC_INT_INTERVAL (60 * HZ)
|
|
|
|
/* Maximum time to wait for U-Boot and DDR to be ready with ms. */
|
|
#define LS2K_BMC_RESET_WAIT_TIME 10000
|
|
|
|
/* It's an experience value */
|
|
#define LS7A_BAR0_CHECK_MAX_TIMES 2000
|
|
|
|
#define PCI_REG_STRIDE 0x4
|
|
|
|
#define LS2K_BMC_RESET_GPIO 14
|
|
#define LOONGSON_GPIO_REG_BASE 0x1FE00500
|
|
#define LOONGSON_GPIO_REG_SIZE 0x18
|
|
#define LOONGSON_GPIO_OEN 0x0
|
|
#define LOONGSON_GPIO_FUNC 0x4
|
|
#define LOONGSON_GPIO_INTPOL 0x10
|
|
#define LOONGSON_GPIO_INTEN 0x14
|
|
|
|
#define LOONGSON_IO_INT_BASE 16
|
|
#define LS2K_BMC_RESET_GPIO_INT_VEC (LS2K_BMC_RESET_GPIO % 8)
|
|
#define LS2K_BMC_RESET_GPIO_GSI (LOONGSON_IO_INT_BASE + LS2K_BMC_RESET_GPIO_INT_VEC)
|
|
|
|
enum {
|
|
LS2K_BMC_DISPLAY,
|
|
LS2K_BMC_IPMI0,
|
|
LS2K_BMC_IPMI1,
|
|
LS2K_BMC_IPMI2,
|
|
LS2K_BMC_IPMI3,
|
|
LS2K_BMC_IPMI4,
|
|
};
|
|
|
|
static struct resource ls2k_display_resources[] = {
|
|
DEFINE_RES_MEM_NAMED(LS2K_DISPLAY_RES_START, SZ_4M, "simpledrm-res"),
|
|
};
|
|
|
|
static struct resource ls2k_ipmi0_resources[] = {
|
|
DEFINE_RES_MEM_NAMED(LS2K_IPMI0_RES_START, LS2K_IPMI_RES_SIZE, "ipmi0-res"),
|
|
};
|
|
|
|
static struct resource ls2k_ipmi1_resources[] = {
|
|
DEFINE_RES_MEM_NAMED(LS2K_IPMI1_RES_START, LS2K_IPMI_RES_SIZE, "ipmi1-res"),
|
|
};
|
|
|
|
static struct resource ls2k_ipmi2_resources[] = {
|
|
DEFINE_RES_MEM_NAMED(LS2K_IPMI2_RES_START, LS2K_IPMI_RES_SIZE, "ipmi2-res"),
|
|
};
|
|
|
|
static struct resource ls2k_ipmi3_resources[] = {
|
|
DEFINE_RES_MEM_NAMED(LS2K_IPMI3_RES_START, LS2K_IPMI_RES_SIZE, "ipmi3-res"),
|
|
};
|
|
|
|
static struct resource ls2k_ipmi4_resources[] = {
|
|
DEFINE_RES_MEM_NAMED(LS2K_IPMI4_RES_START, LS2K_IPMI_RES_SIZE, "ipmi4-res"),
|
|
};
|
|
|
|
static struct mfd_cell ls2k_bmc_cells[] = {
|
|
[LS2K_BMC_DISPLAY] = {
|
|
.name = "simple-framebuffer",
|
|
.num_resources = ARRAY_SIZE(ls2k_display_resources),
|
|
.resources = ls2k_display_resources
|
|
},
|
|
[LS2K_BMC_IPMI0] = {
|
|
.name = "ls2k-ipmi-si",
|
|
.num_resources = ARRAY_SIZE(ls2k_ipmi0_resources),
|
|
.resources = ls2k_ipmi0_resources
|
|
},
|
|
[LS2K_BMC_IPMI1] = {
|
|
.name = "ls2k-ipmi-si",
|
|
.num_resources = ARRAY_SIZE(ls2k_ipmi1_resources),
|
|
.resources = ls2k_ipmi1_resources
|
|
},
|
|
[LS2K_BMC_IPMI2] = {
|
|
.name = "ls2k-ipmi-si",
|
|
.num_resources = ARRAY_SIZE(ls2k_ipmi2_resources),
|
|
.resources = ls2k_ipmi2_resources
|
|
},
|
|
[LS2K_BMC_IPMI3] = {
|
|
.name = "ls2k-ipmi-si",
|
|
.num_resources = ARRAY_SIZE(ls2k_ipmi3_resources),
|
|
.resources = ls2k_ipmi3_resources
|
|
},
|
|
[LS2K_BMC_IPMI4] = {
|
|
.name = "ls2k-ipmi-si",
|
|
.num_resources = ARRAY_SIZE(ls2k_ipmi4_resources),
|
|
.resources = ls2k_ipmi4_resources
|
|
},
|
|
};
|
|
|
|
/* Index of the BMC PCI configuration space to be restored at BMC reset. */
|
|
struct ls2k_bmc_pci_data {
|
|
u32 pci_command;
|
|
u32 base_address0;
|
|
u32 interrupt_line;
|
|
};
|
|
|
|
/* Index of the parent PCI configuration space to be restored at BMC reset. */
|
|
struct ls2k_bmc_bridge_pci_data {
|
|
u32 pci_command;
|
|
u32 base_address[6];
|
|
u32 rom_addreess;
|
|
u32 interrupt_line;
|
|
u32 msi_hi;
|
|
u32 msi_lo;
|
|
u32 devctl;
|
|
u32 linkcap;
|
|
u32 linkctl_sts;
|
|
u32 symbol_timer;
|
|
u32 gen2_ctrl;
|
|
};
|
|
|
|
struct ls2k_bmc_ddata {
|
|
struct device *dev;
|
|
struct work_struct bmc_reset_work;
|
|
struct ls2k_bmc_pci_data bmc_pci_data;
|
|
struct ls2k_bmc_bridge_pci_data bridge_pci_data;
|
|
};
|
|
|
|
static bool ls2k_bmc_bar0_addr_is_set(struct pci_dev *pdev)
|
|
{
|
|
u32 addr;
|
|
|
|
pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &addr);
|
|
|
|
return addr & PCI_BASE_ADDRESS_MEM_MASK ? true : false;
|
|
}
|
|
|
|
static bool ls2k_bmc_pcie_is_connected(struct pci_dev *parent, struct ls2k_bmc_ddata *ddata)
|
|
{
|
|
void __iomem *base;
|
|
int val, ret;
|
|
|
|
base = pci_iomap(parent, 0, LS7A_PCI_CFG_SIZE);
|
|
if (!base)
|
|
return false;
|
|
|
|
val = readl(base + LS7A_PCIE_PORT_CTL0);
|
|
writel(val | LS2K_BMC_PCIE_LTSSM_ENABLE, base + LS7A_PCIE_PORT_CTL0);
|
|
|
|
ret = readl_poll_timeout_atomic(base + LS7A_PCIE_PORT_STS1, val,
|
|
(val & LS2K_BMC_PCIE_LTSSM_STS) == LS2K_BMC_PCIE_CONNECTED,
|
|
LS2K_BMC_PCIE_DELAY_US, LS2K_BMC_PCIE_TIMEOUT_US);
|
|
if (ret) {
|
|
pci_iounmap(parent, base);
|
|
dev_err(ddata->dev, "PCI-E training failed status=0x%x\n", val);
|
|
return false;
|
|
}
|
|
|
|
pci_iounmap(parent, base);
|
|
return true;
|
|
}
|
|
|
|
static void ls2k_bmc_restore_bridge_pci_data(struct pci_dev *parent, struct ls2k_bmc_ddata *ddata)
|
|
{
|
|
int base, i = 0;
|
|
|
|
pci_write_config_dword(parent, PCI_COMMAND, ddata->bridge_pci_data.pci_command);
|
|
|
|
for (base = PCI_BASE_ADDRESS_0; base <= PCI_BASE_ADDRESS_5; base += PCI_REG_STRIDE, i++)
|
|
pci_write_config_dword(parent, base, ddata->bridge_pci_data.base_address[i]);
|
|
|
|
pci_write_config_dword(parent, PCI_ROM_ADDRESS, ddata->bridge_pci_data.rom_addreess);
|
|
pci_write_config_dword(parent, PCI_INTERRUPT_LINE, ddata->bridge_pci_data.interrupt_line);
|
|
|
|
pci_write_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_LO,
|
|
ddata->bridge_pci_data.msi_lo);
|
|
pci_write_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_HI,
|
|
ddata->bridge_pci_data.msi_hi);
|
|
pci_write_config_dword(parent, parent->pcie_cap + PCI_EXP_DEVCTL,
|
|
ddata->bridge_pci_data.devctl);
|
|
pci_write_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCAP,
|
|
ddata->bridge_pci_data.linkcap);
|
|
pci_write_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCTL,
|
|
ddata->bridge_pci_data.linkctl_sts);
|
|
|
|
pci_write_config_dword(parent, LS7A_GEN2_CTL, ddata->bridge_pci_data.gen2_ctrl);
|
|
pci_write_config_dword(parent, LS7A_SYMBOL_TIMER, ddata->bridge_pci_data.symbol_timer);
|
|
}
|
|
|
|
static int ls2k_bmc_recover_pci_data(void *data)
|
|
{
|
|
struct ls2k_bmc_ddata *ddata = data;
|
|
struct pci_dev *pdev = to_pci_dev(ddata->dev);
|
|
struct pci_dev *parent = pdev->bus->self;
|
|
u32 i;
|
|
|
|
/*
|
|
* Clear the bus, io and mem resources of the PCI-E bridge to zero, so that
|
|
* the processor can not access the LS2K PCI-E port, to avoid crashing due to
|
|
* the lack of return signal from accessing the LS2K PCI-E port.
|
|
*/
|
|
pci_write_config_dword(parent, PCI_BASE_ADDRESS_2, 0);
|
|
pci_write_config_dword(parent, PCI_BASE_ADDRESS_3, 0);
|
|
pci_write_config_dword(parent, PCI_BASE_ADDRESS_4, 0);
|
|
|
|
/*
|
|
* When the LS2K BMC is reset, the LS7A PCI-E port is also reset, and its PCI
|
|
* BAR0 register is cleared. Due to the time gap between the GPIO interrupt
|
|
* generation and the LS2K BMC reset, the LS7A PCI BAR0 register is read to
|
|
* determine whether the reset has begun.
|
|
*/
|
|
for (i = LS7A_BAR0_CHECK_MAX_TIMES; i > 0 ; i--) {
|
|
if (!ls2k_bmc_bar0_addr_is_set(parent))
|
|
break;
|
|
mdelay(1);
|
|
};
|
|
|
|
if (i == 0)
|
|
return false;
|
|
|
|
ls2k_bmc_restore_bridge_pci_data(parent, ddata);
|
|
|
|
/* Check if PCI-E is connected */
|
|
if (!ls2k_bmc_pcie_is_connected(parent, ddata))
|
|
return false;
|
|
|
|
/* Waiting for U-Boot and DDR ready */
|
|
mdelay(LS2K_BMC_RESET_WAIT_TIME);
|
|
if (!ls2k_bmc_bar0_addr_is_set(parent))
|
|
return false;
|
|
|
|
/* Restore LS2K BMC PCI-E config data */
|
|
pci_write_config_dword(pdev, PCI_COMMAND, ddata->bmc_pci_data.pci_command);
|
|
pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, ddata->bmc_pci_data.base_address0);
|
|
pci_write_config_dword(pdev, PCI_INTERRUPT_LINE, ddata->bmc_pci_data.interrupt_line);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ls2k_bmc_events_fn(struct work_struct *work)
|
|
{
|
|
struct ls2k_bmc_ddata *ddata = container_of(work, struct ls2k_bmc_ddata, bmc_reset_work);
|
|
|
|
/*
|
|
* The PCI-E is lost when the BMC resets, at which point access to the PCI-E
|
|
* from other CPUs is suspended to prevent a crash.
|
|
*/
|
|
stop_machine(ls2k_bmc_recover_pci_data, ddata, NULL);
|
|
|
|
if (IS_ENABLED(CONFIG_VT)) {
|
|
/* Re-push the display due to previous PCI-E loss. */
|
|
set_console(vt_move_to_console(MAX_NR_CONSOLES - 1, 1));
|
|
}
|
|
}
|
|
|
|
static irqreturn_t ls2k_bmc_interrupt(int irq, void *arg)
|
|
{
|
|
struct ls2k_bmc_ddata *ddata = arg;
|
|
static unsigned long last_jiffies;
|
|
|
|
if (system_state != SYSTEM_RUNNING)
|
|
return IRQ_HANDLED;
|
|
|
|
/* Skip interrupt in LS2K_BMC_INT_INTERVAL */
|
|
if (time_after(jiffies, last_jiffies + LS2K_BMC_INT_INTERVAL)) {
|
|
schedule_work(&ddata->bmc_reset_work);
|
|
last_jiffies = jiffies;
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* Saves the BMC parent device (LS7A) and its own PCI configuration space registers
|
|
* that need to be restored after BMC reset.
|
|
*/
|
|
static void ls2k_bmc_save_pci_data(struct pci_dev *pdev, struct ls2k_bmc_ddata *ddata)
|
|
{
|
|
struct pci_dev *parent = pdev->bus->self;
|
|
int base, i = 0;
|
|
|
|
pci_read_config_dword(parent, PCI_COMMAND, &ddata->bridge_pci_data.pci_command);
|
|
|
|
for (base = PCI_BASE_ADDRESS_0; base <= PCI_BASE_ADDRESS_5; base += PCI_REG_STRIDE, i++)
|
|
pci_read_config_dword(parent, base, &ddata->bridge_pci_data.base_address[i]);
|
|
|
|
pci_read_config_dword(parent, PCI_ROM_ADDRESS, &ddata->bridge_pci_data.rom_addreess);
|
|
pci_read_config_dword(parent, PCI_INTERRUPT_LINE, &ddata->bridge_pci_data.interrupt_line);
|
|
|
|
pci_read_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_LO,
|
|
&ddata->bridge_pci_data.msi_lo);
|
|
pci_read_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_HI,
|
|
&ddata->bridge_pci_data.msi_hi);
|
|
|
|
pci_read_config_dword(parent, parent->pcie_cap + PCI_EXP_DEVCTL,
|
|
&ddata->bridge_pci_data.devctl);
|
|
pci_read_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCAP,
|
|
&ddata->bridge_pci_data.linkcap);
|
|
pci_read_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCTL,
|
|
&ddata->bridge_pci_data.linkctl_sts);
|
|
|
|
pci_read_config_dword(parent, LS7A_GEN2_CTL, &ddata->bridge_pci_data.gen2_ctrl);
|
|
ddata->bridge_pci_data.gen2_ctrl |= FIELD_PREP(LS7A_GEN2_SPEED_CHANG, 0x1) |
|
|
FIELD_PREP(LS7A_CONF_PHY_TX, 0x0);
|
|
|
|
pci_read_config_dword(parent, LS7A_SYMBOL_TIMER, &ddata->bridge_pci_data.symbol_timer);
|
|
ddata->bridge_pci_data.symbol_timer |= LS7A_MASK_LEN_MATCH;
|
|
|
|
pci_read_config_dword(pdev, PCI_COMMAND, &ddata->bmc_pci_data.pci_command);
|
|
pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &ddata->bmc_pci_data.base_address0);
|
|
pci_read_config_dword(pdev, PCI_INTERRUPT_LINE, &ddata->bmc_pci_data.interrupt_line);
|
|
}
|
|
|
|
static int ls2k_bmc_init(struct ls2k_bmc_ddata *ddata)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(ddata->dev);
|
|
void __iomem *gpio_base;
|
|
int gpio_irq, ret, val;
|
|
|
|
ls2k_bmc_save_pci_data(pdev, ddata);
|
|
|
|
INIT_WORK(&ddata->bmc_reset_work, ls2k_bmc_events_fn);
|
|
|
|
ret = devm_request_irq(&pdev->dev, pdev->irq, ls2k_bmc_interrupt,
|
|
IRQF_SHARED | IRQF_TRIGGER_FALLING, "ls2kbmc pcie", ddata);
|
|
if (ret) {
|
|
dev_err(ddata->dev, "Failed to request LS2KBMC PCI-E IRQ %d.\n", pdev->irq);
|
|
return ret;
|
|
}
|
|
|
|
gpio_base = ioremap(LOONGSON_GPIO_REG_BASE, LOONGSON_GPIO_REG_SIZE);
|
|
if (!gpio_base)
|
|
return -ENOMEM;
|
|
|
|
/* Disable GPIO output */
|
|
val = readl(gpio_base + LOONGSON_GPIO_OEN);
|
|
writel(val | BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_OEN);
|
|
|
|
/* Enable GPIO functionality */
|
|
val = readl(gpio_base + LOONGSON_GPIO_FUNC);
|
|
writel(val & ~BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_FUNC);
|
|
|
|
/* Set GPIO interrupts to low-level active */
|
|
val = readl(gpio_base + LOONGSON_GPIO_INTPOL);
|
|
writel(val & ~BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_INTPOL);
|
|
|
|
/* Enable GPIO interrupts */
|
|
val = readl(gpio_base + LOONGSON_GPIO_INTEN);
|
|
writel(val | BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_INTEN);
|
|
|
|
iounmap(gpio_base);
|
|
|
|
/*
|
|
* Since gpio_chip->to_irq is not implemented in the Loongson-3 GPIO driver,
|
|
* acpi_register_gsi() is used to obtain the GPIO IRQ. The GPIO interrupt is a
|
|
* watchdog interrupt that is triggered when the BMC resets.
|
|
*/
|
|
gpio_irq = acpi_register_gsi(NULL, LS2K_BMC_RESET_GPIO_GSI, ACPI_EDGE_SENSITIVE,
|
|
ACPI_ACTIVE_LOW);
|
|
if (gpio_irq < 0)
|
|
return gpio_irq;
|
|
|
|
ret = devm_request_irq(ddata->dev, gpio_irq, ls2k_bmc_interrupt,
|
|
IRQF_SHARED | IRQF_TRIGGER_FALLING, "ls2kbmc gpio", ddata);
|
|
if (ret)
|
|
dev_err(ddata->dev, "Failed to request LS2KBMC GPIO IRQ %d.\n", gpio_irq);
|
|
|
|
acpi_unregister_gsi(LS2K_BMC_RESET_GPIO_GSI);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Currently the Loongson-2K BMC hardware does not have an I2C interface to adapt to the
|
|
* resolution. We set the resolution by presetting "video=1280x1024-16@2M" to the BMC memory.
|
|
*/
|
|
static int ls2k_bmc_parse_mode(struct pci_dev *pdev, struct simplefb_platform_data *pd)
|
|
{
|
|
char *mode;
|
|
int depth, ret;
|
|
|
|
/* The last 16M of PCI BAR0 is used to store the resolution string. */
|
|
mode = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 0) + SZ_16M, SZ_16M);
|
|
if (!mode)
|
|
return -ENOMEM;
|
|
|
|
/* The resolution field starts with the flag "video=". */
|
|
if (!strncmp(mode, "video=", 6))
|
|
mode = mode + 6;
|
|
|
|
ret = kstrtoint(strsep(&mode, "x"), 10, &pd->width);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = kstrtoint(strsep(&mode, "-"), 10, &pd->height);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = kstrtoint(strsep(&mode, "@"), 10, &depth);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pd->stride = pd->width * depth / 8;
|
|
pd->format = depth == 32 ? "a8r8g8b8" : "r5g6b5";
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls2k_bmc_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|
{
|
|
struct simplefb_platform_data pd;
|
|
struct ls2k_bmc_ddata *ddata;
|
|
resource_size_t base;
|
|
int ret;
|
|
|
|
ret = pci_enable_device(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ddata = devm_kzalloc(&dev->dev, sizeof(*ddata), GFP_KERNEL);
|
|
if (!ddata) {
|
|
ret = -ENOMEM;
|
|
goto disable_pci;
|
|
}
|
|
|
|
ddata->dev = &dev->dev;
|
|
|
|
ret = ls2k_bmc_init(ddata);
|
|
if (ret)
|
|
goto disable_pci;
|
|
|
|
ret = ls2k_bmc_parse_mode(dev, &pd);
|
|
if (ret)
|
|
goto disable_pci;
|
|
|
|
ls2k_bmc_cells[LS2K_BMC_DISPLAY].platform_data = &pd;
|
|
ls2k_bmc_cells[LS2K_BMC_DISPLAY].pdata_size = sizeof(pd);
|
|
base = dev->resource[0].start + LS2K_DISPLAY_RES_START;
|
|
|
|
/* Remove conflicting efifb device */
|
|
ret = aperture_remove_conflicting_devices(base, SZ_4M, "simple-framebuffer");
|
|
if (ret) {
|
|
dev_err(&dev->dev, "Failed to removed firmware framebuffers: %d\n", ret);
|
|
goto disable_pci;
|
|
}
|
|
|
|
ret = devm_mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO,
|
|
ls2k_bmc_cells, ARRAY_SIZE(ls2k_bmc_cells),
|
|
&dev->resource[0], 0, NULL);
|
|
if (ret)
|
|
goto disable_pci;
|
|
|
|
return 0;
|
|
|
|
disable_pci:
|
|
pci_disable_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
static void ls2k_bmc_remove(struct pci_dev *dev)
|
|
{
|
|
pci_disable_device(dev);
|
|
}
|
|
|
|
static struct pci_device_id ls2k_bmc_devices[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x1a05) },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, ls2k_bmc_devices);
|
|
|
|
static struct pci_driver ls2k_bmc_driver = {
|
|
.name = "ls2k-bmc",
|
|
.id_table = ls2k_bmc_devices,
|
|
.probe = ls2k_bmc_probe,
|
|
.remove = ls2k_bmc_remove,
|
|
};
|
|
module_pci_driver(ls2k_bmc_driver);
|
|
|
|
MODULE_DESCRIPTION("Loongson-2K Board Management Controller (BMC) Core driver");
|
|
MODULE_AUTHOR("Loongson Technology Corporation Limited");
|
|
MODULE_LICENSE("GPL");
|