@@ -8,10 +8,12 @@
*/
#include <linux/aperture.h>
+#include <linux/delay.h>
#include <linux/minmax.h>
#include <linux/pci.h>
#include <linux/platform_data/simplefb.h>
#include <linux/platform_device.h>
+#include <linux/stop_machine.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_state_helper.h>
@@ -32,9 +34,27 @@
#include <drm/drm_panic.h>
#include <drm/drm_probe_helper.h>
+#define BMC_RESET_DELAY (60 * HZ)
+#define BMC_RESET_WAIT 10000
+
+static const u32 index[] = { 0x4, 0x10, 0x14, 0x18, 0x1c, 0x20, 0x24,
+ 0x30, 0x3c, 0x54, 0x58, 0x78, 0x7c, 0x80 };
+static const u32 cindex[] = { 0x4, 0x10, 0x3c };
+
+struct ls2kbmc_pci_data {
+ u32 d80c;
+ u32 d71c;
+ u32 data[14];
+ u32 cdata[3];
+};
+
struct ls2kbmc_pdata {
struct pci_dev *pdev;
+ struct drm_device *ddev;
+ struct work_struct bmc_work;
+ unsigned long reset_time;
struct simplefb_platform_data pd;
+ struct ls2kbmc_pci_data pci_data;
};
/*
@@ -583,6 +603,265 @@ static struct ls2kbmc_device *ls2kbmc_device_create(struct drm_driver *drv,
return sdev;
}
+static bool ls2kbmc_bar0_addr_is_set(struct pci_dev *ppdev)
+{
+ u32 addr;
+
+ pci_read_config_dword(ppdev, PCI_BASE_ADDRESS_0, &addr);
+ addr &= PCI_BASE_ADDRESS_MEM_MASK;
+
+ return addr ? true : false;
+}
+
+static void ls2kbmc_save_pci_data(struct ls2kbmc_pdata *priv)
+{
+ struct pci_dev *pdev = priv->pdev;
+ struct pci_dev *parent = pdev->bus->self;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(index); i++)
+ pci_read_config_dword(parent, index[i], &priv->pci_data.data[i]);
+
+ for (i = 0; i < ARRAY_SIZE(cindex); i++)
+ pci_read_config_dword(pdev, cindex[i], &priv->pci_data.cdata[i]);
+
+ pci_read_config_dword(parent, 0x80c, &priv->pci_data.d80c);
+ priv->pci_data.d80c = (priv->pci_data.d80c & ~(3 << 17)) | (1 << 17);
+
+ pci_read_config_dword(parent, 0x71c, &priv->pci_data.d71c);
+ priv->pci_data.d71c |= 1 << 26;
+}
+
+static bool ls2kbmc_check_pcie_connected(struct pci_dev *parent, struct drm_device *dev)
+{
+ void __iomem *mmio;
+ int sts, timeout = 10000;
+
+ mmio = pci_iomap(parent, 0, 0x100);
+ if (!mmio)
+ return false;
+
+ writel(readl(mmio) | 0x8, mmio);
+ while (timeout) {
+ sts = readl(mmio + 0xc);
+ if ((sts & 0x11) == 0x11)
+ break;
+ mdelay(1);
+ timeout--;
+ }
+
+ pci_iounmap(parent, mmio);
+
+ if (!timeout) {
+ drm_err(dev, "pcie train failed status=0x%x\n", sts);
+ return false;
+ }
+
+ return true;
+}
+
+static int ls2kbmc_recove_pci_data(void *data)
+{
+ struct ls2kbmc_pdata *priv = data;
+ struct pci_dev *pdev = priv->pdev;
+ struct drm_device *dev = priv->ddev;
+ struct pci_dev *parent = pdev->bus->self;
+ u32 i, timeout, retry = 0;
+ bool ready;
+
+ 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);
+
+ timeout = 10000;
+ while (timeout) {
+ ready = ls2kbmc_bar0_addr_is_set(parent);
+ if (!ready)
+ break;
+ mdelay(1);
+ timeout--;
+ };
+
+ if (!timeout)
+ drm_warn(dev, "bar not clear 0\n");
+
+retrain:
+ for (i = 0; i < ARRAY_SIZE(index); i++)
+ pci_write_config_dword(parent, index[i], priv->pci_data.data[i]);
+
+ pci_write_config_dword(parent, 0x80c, priv->pci_data.d80c);
+ pci_write_config_dword(parent, 0x71c, priv->pci_data.d71c);
+
+ /* Check if the pcie is connected */
+ ready = ls2kbmc_check_pcie_connected(parent, dev);
+ if (!ready)
+ return ready;
+
+ for (i = 0; i < ARRAY_SIZE(cindex); i++)
+ pci_write_config_dword(pdev, cindex[i], priv->pci_data.cdata[i]);
+
+ drm_info(dev, "pcie recovered done\n");
+
+ if (!retry) {
+ /*wait u-boot ddr config */
+ mdelay(BMC_RESET_WAIT);
+ ready = ls2kbmc_bar0_addr_is_set(parent);
+ if (!ready) {
+ retry = 1;
+ goto retrain;
+ }
+ }
+
+ return 0;
+}
+
+static int ls2kbmc_push_drm_mode(struct drm_device *dev)
+{
+ struct ls2kbmc_device *sdev = ls2kbmc_device_of_dev(dev);
+ struct drm_crtc *crtc = &sdev->crtc;
+ struct drm_plane *plane = crtc->primary;
+ struct drm_connector *connector = &sdev->connector;
+ struct drm_framebuffer *fb = NULL;
+ struct drm_mode_set set;
+ struct drm_modeset_acquire_ctx ctx;
+ int ret;
+
+ mutex_lock(&dev->mode_config.mutex);
+ connector->funcs->fill_modes(connector, 4096, 4096);
+ mutex_unlock(&dev->mode_config.mutex);
+
+ DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx,
+ DRM_MODESET_ACQUIRE_INTERRUPTIBLE, ret);
+
+ if (plane->state)
+ fb = plane->state->fb;
+ else
+ fb = plane->fb;
+
+ if (!fb) {
+ drm_dbg(dev, "CRTC doesn't have current FB\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ drm_framebuffer_get(fb);
+
+ set.crtc = crtc;
+ set.x = 0;
+ set.y = 0;
+ set.mode = &sdev->mode;
+ set.connectors = &connector;
+ set.num_connectors = 1;
+ set.fb = fb;
+
+ ret = crtc->funcs->set_config(&set, &ctx);
+
+out:
+ DRM_MODESET_LOCK_ALL_END(dev, ctx, ret);
+
+ return ret;
+}
+
+static void ls2kbmc_events_fn(struct work_struct *work)
+{
+ struct ls2kbmc_pdata *priv = container_of(work, struct ls2kbmc_pdata, bmc_work);
+
+ /*
+ * The pcie is lost when the BMC resets,
+ * at which point access to the pcie from other CPUs
+ * is suspended to prevent a crash.
+ */
+ stop_machine(ls2kbmc_recove_pci_data, priv, NULL);
+
+ drm_info(priv->ddev, "redraw console\n");
+
+ /* We need to re-push the display due to previous pcie loss. */
+ ls2kbmc_push_drm_mode(priv->ddev);
+}
+
+static irqreturn_t ls2kbmc_interrupt(int irq, void *arg)
+{
+ struct ls2kbmc_pdata *priv = arg;
+
+ if (system_state != SYSTEM_RUNNING)
+ return IRQ_HANDLED;
+
+ /* skip interrupt in BMC_RESET_DELAY */
+ if (time_after(jiffies, priv->reset_time + BMC_RESET_DELAY))
+ schedule_work(&priv->bmc_work);
+
+ priv->reset_time = jiffies;
+
+ return IRQ_HANDLED;
+}
+
+#define 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
+
+/* The gpio interrupt is a watchdog interrupt that is triggered when the BMC resets. */
+static int ls2kbmc_gpio_reset_handler(struct ls2kbmc_pdata *priv)
+{
+ int irq, ret = 0;
+ int gsi = 16 + (BMC_RESET_GPIO & 7);
+ void __iomem *gpio_base;
+
+ /* Since Loongson-3A hardware does not support GPIO interrupt cascade,
+ * chip->gpio_to_irq() cannot be implemented,
+ * here acpi_register_gsi() is used to get gpio irq.
+ */
+ irq = acpi_register_gsi(NULL, gsi, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_LOW);
+ if (irq < 0)
+ return irq;
+
+ gpio_base = ioremap(LOONGSON_GPIO_REG_BASE, LOONGSON_GPIO_REG_SIZE);
+ if (!gpio_base) {
+ acpi_unregister_gsi(gsi);
+ return PTR_ERR(gpio_base);
+ }
+
+ writel(readl(gpio_base + LOONGSON_GPIO_OEN) | BIT(BMC_RESET_GPIO),
+ gpio_base + LOONGSON_GPIO_OEN);
+ writel(readl(gpio_base + LOONGSON_GPIO_FUNC) & ~BIT(BMC_RESET_GPIO),
+ gpio_base + LOONGSON_GPIO_FUNC);
+ writel(readl(gpio_base + LOONGSON_GPIO_INTPOL) & ~BIT(BMC_RESET_GPIO),
+ gpio_base + LOONGSON_GPIO_INTPOL);
+ writel(readl(gpio_base + LOONGSON_GPIO_INTEN) | BIT(BMC_RESET_GPIO),
+ gpio_base + LOONGSON_GPIO_INTEN);
+
+ ret = request_irq(irq, ls2kbmc_interrupt, IRQF_SHARED | IRQF_TRIGGER_FALLING,
+ "ls2kbmc gpio", priv);
+
+ acpi_unregister_gsi(gsi);
+ iounmap(gpio_base);
+
+ return ret;
+}
+
+static int ls2kbmc_pdata_initial(struct platform_device *pdev, struct ls2kbmc_pdata *priv)
+{
+ int ret;
+
+ priv->pdev = *(struct pci_dev **)dev_get_platdata(&pdev->dev);
+
+ ls2kbmc_save_pci_data(priv);
+
+ INIT_WORK(&priv->bmc_work, ls2kbmc_events_fn);
+
+ ret = request_irq(priv->pdev->irq, ls2kbmc_interrupt,
+ IRQF_SHARED | IRQF_TRIGGER_RISING, "ls2kbmc pcie", priv);
+ if (ret) {
+ pr_err("request_irq(%d) failed\n", priv->pdev->irq);
+ return ret;
+ }
+
+ return ls2kbmc_gpio_reset_handler(priv);
+}
+
/*
* Platform driver
*/
@@ -598,12 +877,15 @@ static int ls2kbmc_probe(struct platform_device *pdev)
if (IS_ERR(priv))
return -ENOMEM;
- priv->pdev = *(struct pci_dev **)dev_get_platdata(&pdev->dev);
+ ret = ls2kbmc_pdata_initial(pdev, priv);
+ if (ret)
+ return ret;
sdev = ls2kbmc_device_create(&ls2kbmc_driver, pdev, priv);
if (IS_ERR(sdev))
return PTR_ERR(sdev);
dev = &sdev->dev;
+ priv->ddev = &sdev->dev;
ret = drm_dev_register(dev, 0);
if (ret)