Hi

Am 30.12.24 um 10:31 schrieb Binbin Zhou:
Since the display is a sub-function of the Loongson-2K BMC, when the
BMC reset, the entire BMC PCIe is disconnected, including the display
which is interrupted.

To me, that's a strong indicator to set up the entire thing from scratch.


Not only do you need to save/restore the relevant PCIe registers
throughout the reset process, but you also need to re-push the display
to the monitor at the end.

Co-developed-by: Chong Qiao <qiaoch...@loongson.cn>
Signed-off-by: Chong Qiao <qiaoch...@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubin...@loongson.cn>
---
  drivers/gpu/drm/tiny/ls2kbmc.c | 284 ++++++++++++++++++++++++++++++++-
  1 file changed, 283 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/tiny/ls2kbmc.c b/drivers/gpu/drm/tiny/ls2kbmc.c
index 909d6c687193..4b440f20cb4d 100644
--- a/drivers/gpu/drm/tiny/ls2kbmc.c
+++ b/drivers/gpu/drm/tiny/ls2kbmc.c

Move all the reset detection into the BMC core driver. When you see a reset, remove the display's platform device via platform_device_unregister(). This will release the deviceĀ  and the DRM driver on top. We do this for simpledrm/efifb/etc. Hence user-space code is able to deal with it. Then set up a new platform device when the new framebuffer is available. Your DRM driver will bind to it and user-space code will continue with the new DRM device.

Best regards
Thomas

@@ -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)

--
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstrasse 146, 90461 Nuernberg, Germany
GF: Ivo Totev, Andrew Myers, Andrew McDonald, Boudien Moerman
HRB 36809 (AG Nuernberg)



_______________________________________________
Openipmi-developer mailing list
Openipmi-developer@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openipmi-developer

Reply via email to