Re: [PATCH v1 2/2] drm/tegra: Use drm_dp_aux_register_ddc/chardev() helpers

Dmitry Osipenko
2021-11-09T14:08:17Z

09.11.2021 16:52, Dmitry Osipenko пишет:
> 09.11.2021 12:19, Daniel Vetter пишет:
>> On Mon, Nov 08, 2021 at 09:16:07PM +0300, Dmitry Osipenko wrote:
>>> 08.11.2021 18:17, Daniel Vetter пишет:
>>>> On Mon, Nov 08, 2021 at 02:08:21AM +0300, Dmitry Osipenko wrote:
>>>>> Use drm_dp_aux_register_ddc/chardev() helpers that allow to register I2C
>>>>> adapter separately from the character device. This fixes broken display
>>>>> panel driver of Acer Chromebook CB5-311 that fails to probe starting with
>>>>> v5.13 kernel when DP AUX registration order was changed. Tegra SOR driver
>>>>> is never probed now using the new registration order because tegra-output
>>>>> always fails with -EPROBE_DEFER due to missing display panel that requires
>>>>> DP AUX DDC to be registered first. The offending commit made DDC to be
>>>>> registered after SOR's output, which can't ever happen. Use new helpers
>>>>> to restore the registration order and revive display panel.
>>>>
>>>> This feels a bit backward, I think the clean solution would be to untangle
>>>> the SOR loading from the panel driver loading, and then only block
>>>> registering the overall drm_device on both drivers having loaded.
>>>
>>> Sounds impossible.
>>>
>>> 1. DRM device can be created only when all components are ready, panel
>>> is one of the components.
>>
>> Nope. drm_device can be instantiated whenever you feel like.
>> drm_dev_register can only be called when it's all fully set up. Absolutely
>> nothing would work if drm_device wouldn't support this two-stage setup.
>>
>> So sequence:
>>
>> 1. drm_dev_init
>>
>> 2. bind sor driver
>>
>> 3. create dp aux ddc
>>
>> 4. bind panel
>>
>> 5. yay we have everything, drm_dev_register
>>
>> This should work, and it's designed to work like this actually. You
>> couldn't write big complex drivers otherwise.
>
> All Tegra SoCs have graphics bus named Host1x, that is where components
> comprising DRM driver are sitting on.
>
> The Tegra driver model works like this:
>
> 1. Once Host1x driver is loaded, it populates children sub-nodes of
> Host1x device-tree node, this creates Tegra DRM platform sub-devices.
>
> 2. Tegra DRM driver module-init call registers main "Host1x DRM" driver
> and platform sub-drivers. Now Host1x bus knows what devices comprise
> Tegra DRM because Host1x driver descriptor contains that info.
>
> 3. Tegra DRM platform sub-drivers are bound to the sub-devices.
>
> 4. Once Host1x bus sees that all Tegra DRM sub-drivers are bound, it
> creates Host1x DRM device.
>
> 5. The Host1x DRM device is bound to the Host1x DRM driver and
> host1x_drm_probe(host1x_dev) is invoked, which does the following:
>
> drm_dev_alloc(driver, host1x_dev)
> host1x_device_init(host1x_dev)
> drm_dev_register(drm).
>
> 6. The host1x_device_init() invokes second stage initialization of Tegra
> DRM sub-drivers, that is init() callback of host1x_client_ops registered
> by DRM platform sub-drivers during theirs probe.
>
> The DP AUX DDC is currently created in step 6, while it should be
> created in step 3, otherwise SOR driver can't be bound.
>
> It's possible to add early-init stage to the Host1x driver model where
> DRM device can be created before DRM platform sub-drivers are registered
> and probed. This is ad-hoc solution, but it should work okay in practice.
>
> I can make v2 if you and Thierry are okay with that solution, see it below.
>
> --- 8< ---
>
> diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c
> index 1f96e416fa08..9e17a75a1383 100644
> --- a/drivers/gpu/drm/tegra/dpaux.c
> +++ b/drivers/gpu/drm/tegra/dpaux.c
> @@ -530,9 +530,12 @@ static int tegra_dpaux_probe(struct platform_device
> *pdev)
> disable_irq(dpaux->irq);
>
> dpaux->aux.transfer = tegra_dpaux_transfer;
> + dpaux->aux.drm_dev = tegra_drm_device();
> dpaux->aux.dev = &pdev->dev;
>
> - drm_dp_aux_init(&dpaux->aux);
> + err = drm_dp_aux_register(&dpaux->aux);
> + if (err < 0)
> + return err;
>
> /*
> * Assume that by default the DPAUX/I2C pads will be used for HDMI,
> @@ -585,6 +588,8 @@ static int tegra_dpaux_remove(struct platform_device
> *pdev)
> pm_runtime_put_sync(&pdev->dev);
> pm_runtime_disable(&pdev->dev);
>
> + drm_dp_aux_unregister(&dpaux->aux);
> +
> mutex_lock(&dpaux_lock);
> list_del(&dpaux->list);
> mutex_unlock(&dpaux_lock);
> @@ -717,11 +722,6 @@ int drm_dp_aux_attach(struct drm_dp_aux *aux,
> struct tegra_output *output)
> unsigned long timeout;
> int err;
>
> - aux->drm_dev = output->connector.dev;
> - err = drm_dp_aux_register(aux);
> - if (err < 0)
> - return err;
> -
> output->connector.polled = DRM_CONNECTOR_POLL_HPD;
> dpaux->output = output;
>
> @@ -759,7 +759,6 @@ int drm_dp_aux_detach(struct drm_dp_aux *aux)
> unsigned long timeout;
> int err;
>
> - drm_dp_aux_unregister(aux);
> disable_irq(dpaux->irq);
>
> if (dpaux->output->panel) {
> diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c
> index b2ebba802107..d95f9dea6b86 100644
> --- a/drivers/gpu/drm/tegra/drm.c
> +++ b/drivers/gpu/drm/tegra/drm.c
> @@ -1124,21 +1124,42 @@ static bool host1x_drm_wants_iommu(struct
> host1x_device *dev)
> return domain != NULL;
> }
>
> -static int host1x_drm_probe(struct host1x_device *dev)
> +static struct drm_device *terga_drm_dev;
> +
> +struct drm_device *tegra_drm_device(void)
> {
> - struct tegra_drm *tegra;
> - struct drm_device *drm;
> - int err;
> + return terga_drm_dev;
> +}
>
> - drm = drm_dev_alloc(&tegra_drm_driver, &dev->dev);
> +static int host1x_drm_dev_init(struct host1x_device *dev)
> +{
> + struct drm_device *drm = drm_dev_alloc(&tegra_drm_driver, &dev->dev);
> if (IS_ERR(drm))
> return PTR_ERR(drm);
>
> + dev_set_drvdata(&dev->dev, drm);
> + terga_drm_dev = drm;

Although, platform_register_drivers() should be moved here out from
host1x_drm_init(), otherwise DRM drivers may get probed before main
host1x driver is registered and then terga_drm_dev will be NULL. But you
should get the idea anyways.

> + return 0;
> +}
> +
> +static void host1x_drm_dev_deinit(struct host1x_device *dev)
> +{
> + struct drm_device *drm = dev_get_drvdata(&dev->dev);

And platform_unregister_drivers() should be moved here.

> + terga_drm_dev = NULL;
> + drm_dev_put(drm);
> +}
> +
> +static int host1x_drm_probe(struct host1x_device *dev)
> +{
> + struct drm_device *drm = dev_get_drvdata(&dev->dev);
> + struct tegra_drm *tegra;
> + int err;
> +
> tegra = kzalloc(sizeof(*tegra), GFP_KERNEL);
> - if (!tegra) {
> - err = -ENOMEM;
> - goto put;
> - }
> + if (!tegra)
> + return -ENOMEM;
>
> if (host1x_drm_wants_iommu(dev) && iommu_present(&platform_bus_type)) {
> tegra->domain = iommu_domain_alloc(&platform_bus_type);
> @@ -1155,7 +1176,6 @@ static int host1x_drm_probe(struct host1x_device *dev)
> mutex_init(&tegra->clients_lock);
> INIT_LIST_HEAD(&tegra->clients);
>
> - dev_set_drvdata(&dev->dev, drm);
> drm->dev_private = tegra;
> tegra->drm = drm;
>
> @@ -1276,8 +1296,7 @@ static int host1x_drm_probe(struct host1x_device *dev)
> iommu_domain_free(tegra->domain);
> free:
> kfree(tegra);
> -put:
> - drm_dev_put(drm);
> +
> return err;
> }
>
> @@ -1310,7 +1329,6 @@ static int host1x_drm_remove(struct host1x_device
> *dev)
> }
>
> kfree(tegra);
> - drm_dev_put(drm);
>
> return 0;
> }
> @@ -1382,6 +1400,8 @@ static struct host1x_driver host1x_drm_driver = {
> .probe = host1x_drm_probe,
> .remove = host1x_drm_remove,
> .subdevs = host1x_drm_subdevs,
> + .init = host1x_drm_dev_init,
> + .deinit = host1x_drm_dev_deinit,
> };
>
> static struct platform_driver * const drivers[] = {
> diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h
> index fc0a19554eac..4bfe79b5c7ce 100644
> --- a/drivers/gpu/drm/tegra/drm.h
> +++ b/drivers/gpu/drm/tegra/drm.h
> @@ -64,6 +64,8 @@ struct tegra_drm {
> struct tegra_display_hub *hub;
> };
>
> +struct drm_device *tegra_drm_device(void);
> +
> static inline struct host1x *tegra_drm_to_host1x(struct tegra_drm *tegra)
> {
> return dev_get_drvdata(tegra->drm->dev->parent);
> diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c
> index 0d81eede1217..25d688e5c742 100644
> --- a/drivers/gpu/host1x/bus.c
> +++ b/drivers/gpu/host1x/bus.c
> @@ -479,8 +479,18 @@ static int host1x_device_add(struct host1x *host1x,
> device->dev.dma_parms = &device->dma_parms;
> dma_set_max_seg_size(&device->dev, UINT_MAX);
>
> + if (driver->init) {
> + err = driver->init(device);
> + if (err < 0) {
> + kfree(device);
> + return err;
> + }
> + }
> +
> err = host1x_device_parse_dt(device, driver);
> if (err < 0) {
> + if (driver->deinit)
> + driver->deinit(device);
> kfree(device);
> return err;
> }
> @@ -512,11 +522,16 @@ static int host1x_device_add(struct host1x *host1x,
> static void host1x_device_del(struct host1x *host1x,
> struct host1x_device *device)
> {
> + struct host1x_driver *driver = device->driver;
> +
> if (device->registered) {
> device->registered = false;
> device_del(&device->dev);
> }
>
> + if (driver->deinit)
> + driver->deinit(device);
> +
> put_device(&device->dev);
> }
>
> diff --git a/include/linux/host1x.h b/include/linux/host1x.h
> index e8dc5bc41f79..5e5ba33af4ae 100644
> --- a/include/linux/host1x.h
> +++ b/include/linux/host1x.h
> @@ -346,6 +346,8 @@ struct host1x_device;
> * @driver: core driver
> * @subdevs: table of OF device IDs matching subdevices for this driver
> * @list: list node for the driver
> + * @init: called when the host1x logical driver is registered
> + * @deinit: called when the host1x logical driver is unregistered
> * @probe: called when the host1x logical device is probed
> * @remove: called when the host1x logical device is removed
> * @shutdown: called when the host1x logical device is shut down
> @@ -356,6 +358,8 @@ struct host1x_driver {
> const struct of_device_id *subdevs;
> struct list_head list;
>
> + int (*init)(struct host1x_device *device);
> + void (*deinit)(struct host1x_device *device);
> int (*probe)(struct host1x_device *device);
> int (*remove)(struct host1x_device *device);
> void (*shutdown)(struct host1x_device *device);
>