xref: /linux/drivers/gpu/drm/omapdrm/dss/sdi.c (revision 88bc4178568b8e0331143cc0616640ab72f0cba1)
19960aa7cSTomi Valkeinen /*
29960aa7cSTomi Valkeinen  * Copyright (C) 2009 Nokia Corporation
36505d75cSTomi Valkeinen  * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
49960aa7cSTomi Valkeinen  *
59960aa7cSTomi Valkeinen  * This program is free software; you can redistribute it and/or modify it
69960aa7cSTomi Valkeinen  * under the terms of the GNU General Public License version 2 as published by
79960aa7cSTomi Valkeinen  * the Free Software Foundation.
89960aa7cSTomi Valkeinen  *
99960aa7cSTomi Valkeinen  * This program is distributed in the hope that it will be useful, but WITHOUT
109960aa7cSTomi Valkeinen  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
119960aa7cSTomi Valkeinen  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
129960aa7cSTomi Valkeinen  * more details.
139960aa7cSTomi Valkeinen  *
149960aa7cSTomi Valkeinen  * You should have received a copy of the GNU General Public License along with
159960aa7cSTomi Valkeinen  * this program.  If not, see <http://www.gnu.org/licenses/>.
169960aa7cSTomi Valkeinen  */
179960aa7cSTomi Valkeinen 
189960aa7cSTomi Valkeinen #define DSS_SUBSYS_NAME "SDI"
199960aa7cSTomi Valkeinen 
209960aa7cSTomi Valkeinen #include <linux/kernel.h>
219960aa7cSTomi Valkeinen #include <linux/delay.h>
229960aa7cSTomi Valkeinen #include <linux/err.h>
239960aa7cSTomi Valkeinen #include <linux/regulator/consumer.h>
249960aa7cSTomi Valkeinen #include <linux/export.h>
259960aa7cSTomi Valkeinen #include <linux/platform_device.h>
269960aa7cSTomi Valkeinen #include <linux/string.h>
279960aa7cSTomi Valkeinen #include <linux/of.h>
289960aa7cSTomi Valkeinen 
2932043da7SPeter Ujfalusi #include "omapdss.h"
309960aa7cSTomi Valkeinen #include "dss.h"
319960aa7cSTomi Valkeinen 
3224aac601SLaurent Pinchart struct sdi_device {
339960aa7cSTomi Valkeinen 	struct platform_device *pdev;
34d7157dfeSLaurent Pinchart 	struct dss_device *dss;
359960aa7cSTomi Valkeinen 
369960aa7cSTomi Valkeinen 	bool update_enabled;
379960aa7cSTomi Valkeinen 	struct regulator *vdds_sdi_reg;
389960aa7cSTomi Valkeinen 
399960aa7cSTomi Valkeinen 	struct dss_lcd_mgr_config mgr_config;
40e5906f76SLaurent Pinchart 	unsigned long pixelclock;
419960aa7cSTomi Valkeinen 	int datapairs;
429960aa7cSTomi Valkeinen 
439960aa7cSTomi Valkeinen 	struct omap_dss_device output;
4424aac601SLaurent Pinchart };
459960aa7cSTomi Valkeinen 
4624aac601SLaurent Pinchart #define dssdev_to_sdi(dssdev) container_of(dssdev, struct sdi_device, output)
479960aa7cSTomi Valkeinen 
489960aa7cSTomi Valkeinen struct sdi_clk_calc_ctx {
4924aac601SLaurent Pinchart 	struct sdi_device *sdi;
509960aa7cSTomi Valkeinen 	unsigned long pck_min, pck_max;
519960aa7cSTomi Valkeinen 
529960aa7cSTomi Valkeinen 	unsigned long fck;
539960aa7cSTomi Valkeinen 	struct dispc_clock_info dispc_cinfo;
549960aa7cSTomi Valkeinen };
559960aa7cSTomi Valkeinen 
569960aa7cSTomi Valkeinen static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck,
579960aa7cSTomi Valkeinen 		unsigned long pck, void *data)
589960aa7cSTomi Valkeinen {
599960aa7cSTomi Valkeinen 	struct sdi_clk_calc_ctx *ctx = data;
609960aa7cSTomi Valkeinen 
619960aa7cSTomi Valkeinen 	ctx->dispc_cinfo.lck_div = lckd;
629960aa7cSTomi Valkeinen 	ctx->dispc_cinfo.pck_div = pckd;
639960aa7cSTomi Valkeinen 	ctx->dispc_cinfo.lck = lck;
649960aa7cSTomi Valkeinen 	ctx->dispc_cinfo.pck = pck;
659960aa7cSTomi Valkeinen 
669960aa7cSTomi Valkeinen 	return true;
679960aa7cSTomi Valkeinen }
689960aa7cSTomi Valkeinen 
699960aa7cSTomi Valkeinen static bool dpi_calc_dss_cb(unsigned long fck, void *data)
709960aa7cSTomi Valkeinen {
719960aa7cSTomi Valkeinen 	struct sdi_clk_calc_ctx *ctx = data;
729960aa7cSTomi Valkeinen 
739960aa7cSTomi Valkeinen 	ctx->fck = fck;
749960aa7cSTomi Valkeinen 
7524aac601SLaurent Pinchart 	return dispc_div_calc(ctx->sdi->dss->dispc, fck,
768a7eda76SLaurent Pinchart 			      ctx->pck_min, ctx->pck_max,
779960aa7cSTomi Valkeinen 			      dpi_calc_dispc_cb, ctx);
789960aa7cSTomi Valkeinen }
799960aa7cSTomi Valkeinen 
8024aac601SLaurent Pinchart static int sdi_calc_clock_div(struct sdi_device *sdi, unsigned long pclk,
819960aa7cSTomi Valkeinen 			      unsigned long *fck,
829960aa7cSTomi Valkeinen 			      struct dispc_clock_info *dispc_cinfo)
839960aa7cSTomi Valkeinen {
849960aa7cSTomi Valkeinen 	int i;
852bc5ff0bSTomi Valkeinen 	struct sdi_clk_calc_ctx ctx;
869960aa7cSTomi Valkeinen 
879960aa7cSTomi Valkeinen 	/*
889960aa7cSTomi Valkeinen 	 * DSS fclk gives us very few possibilities, so finding a good pixel
899960aa7cSTomi Valkeinen 	 * clock may not be possible. We try multiple times to find the clock,
909960aa7cSTomi Valkeinen 	 * each time widening the pixel clock range we look for, up to
919960aa7cSTomi Valkeinen 	 * +/- 1MHz.
929960aa7cSTomi Valkeinen 	 */
939960aa7cSTomi Valkeinen 
949960aa7cSTomi Valkeinen 	for (i = 0; i < 10; ++i) {
959960aa7cSTomi Valkeinen 		bool ok;
969960aa7cSTomi Valkeinen 
979960aa7cSTomi Valkeinen 		memset(&ctx, 0, sizeof(ctx));
982bc5ff0bSTomi Valkeinen 
992bc5ff0bSTomi Valkeinen 		ctx.sdi = sdi;
1002bc5ff0bSTomi Valkeinen 
1019960aa7cSTomi Valkeinen 		if (pclk > 1000 * i * i * i)
1029960aa7cSTomi Valkeinen 			ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu);
1039960aa7cSTomi Valkeinen 		else
1049960aa7cSTomi Valkeinen 			ctx.pck_min = 0;
1059960aa7cSTomi Valkeinen 		ctx.pck_max = pclk + 1000 * i * i * i;
1069960aa7cSTomi Valkeinen 
10724aac601SLaurent Pinchart 		ok = dss_div_calc(sdi->dss, pclk, ctx.pck_min,
10860f9c59fSLaurent Pinchart 				  dpi_calc_dss_cb, &ctx);
1099960aa7cSTomi Valkeinen 		if (ok) {
1109960aa7cSTomi Valkeinen 			*fck = ctx.fck;
1119960aa7cSTomi Valkeinen 			*dispc_cinfo = ctx.dispc_cinfo;
1129960aa7cSTomi Valkeinen 			return 0;
1139960aa7cSTomi Valkeinen 		}
1149960aa7cSTomi Valkeinen 	}
1159960aa7cSTomi Valkeinen 
1169960aa7cSTomi Valkeinen 	return -EINVAL;
1179960aa7cSTomi Valkeinen }
1189960aa7cSTomi Valkeinen 
11924aac601SLaurent Pinchart static void sdi_config_lcd_manager(struct sdi_device *sdi)
1209960aa7cSTomi Valkeinen {
12124aac601SLaurent Pinchart 	sdi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS;
1229960aa7cSTomi Valkeinen 
12324aac601SLaurent Pinchart 	sdi->mgr_config.stallmode = false;
12424aac601SLaurent Pinchart 	sdi->mgr_config.fifohandcheck = false;
1259960aa7cSTomi Valkeinen 
12624aac601SLaurent Pinchart 	sdi->mgr_config.video_port_width = 24;
12724aac601SLaurent Pinchart 	sdi->mgr_config.lcden_sig_polarity = 1;
1289960aa7cSTomi Valkeinen 
12924aac601SLaurent Pinchart 	dss_mgr_set_lcd_config(&sdi->output, &sdi->mgr_config);
1309960aa7cSTomi Valkeinen }
1319960aa7cSTomi Valkeinen 
13219b4200dSLaurent Pinchart static void sdi_display_enable(struct omap_dss_device *dssdev)
1339960aa7cSTomi Valkeinen {
13424aac601SLaurent Pinchart 	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
1359960aa7cSTomi Valkeinen 	struct dispc_clock_info dispc_cinfo;
13696fc64c7SLaurent Pinchart 	unsigned long fck;
1379960aa7cSTomi Valkeinen 	int r;
1389960aa7cSTomi Valkeinen 
13924aac601SLaurent Pinchart 	r = regulator_enable(sdi->vdds_sdi_reg);
1409960aa7cSTomi Valkeinen 	if (r)
14119b4200dSLaurent Pinchart 		return;
1429960aa7cSTomi Valkeinen 
14324aac601SLaurent Pinchart 	r = dispc_runtime_get(sdi->dss->dispc);
1449960aa7cSTomi Valkeinen 	if (r)
1459960aa7cSTomi Valkeinen 		goto err_get_dispc;
1469960aa7cSTomi Valkeinen 
147e5906f76SLaurent Pinchart 	r = sdi_calc_clock_div(sdi, sdi->pixelclock, &fck, &dispc_cinfo);
1489960aa7cSTomi Valkeinen 	if (r)
1499960aa7cSTomi Valkeinen 		goto err_calc_clock_div;
1509960aa7cSTomi Valkeinen 
15124aac601SLaurent Pinchart 	sdi->mgr_config.clock_info = dispc_cinfo;
1529960aa7cSTomi Valkeinen 
15324aac601SLaurent Pinchart 	r = dss_set_fck_rate(sdi->dss, fck);
1549960aa7cSTomi Valkeinen 	if (r)
1559960aa7cSTomi Valkeinen 		goto err_set_dss_clock_div;
1569960aa7cSTomi Valkeinen 
15724aac601SLaurent Pinchart 	sdi_config_lcd_manager(sdi);
1589960aa7cSTomi Valkeinen 
1599960aa7cSTomi Valkeinen 	/*
1609960aa7cSTomi Valkeinen 	 * LCLK and PCLK divisors are located in shadow registers, and we
1619960aa7cSTomi Valkeinen 	 * normally write them to DISPC registers when enabling the output.
1629960aa7cSTomi Valkeinen 	 * However, SDI uses pck-free as source clock for its PLL, and pck-free
1639960aa7cSTomi Valkeinen 	 * is affected by the divisors. And as we need the PLL before enabling
1649960aa7cSTomi Valkeinen 	 * the output, we need to write the divisors early.
1659960aa7cSTomi Valkeinen 	 *
1669960aa7cSTomi Valkeinen 	 * It seems just writing to the DISPC register is enough, and we don't
1679960aa7cSTomi Valkeinen 	 * need to care about the shadow register mechanism for pck-free. The
1689960aa7cSTomi Valkeinen 	 * exact reason for this is unknown.
1699960aa7cSTomi Valkeinen 	 */
17024aac601SLaurent Pinchart 	dispc_mgr_set_clock_div(sdi->dss->dispc, sdi->output.dispc_channel,
17124aac601SLaurent Pinchart 				&sdi->mgr_config.clock_info);
1729960aa7cSTomi Valkeinen 
17324aac601SLaurent Pinchart 	dss_sdi_init(sdi->dss, sdi->datapairs);
17424aac601SLaurent Pinchart 	r = dss_sdi_enable(sdi->dss);
1759960aa7cSTomi Valkeinen 	if (r)
1769960aa7cSTomi Valkeinen 		goto err_sdi_enable;
1779960aa7cSTomi Valkeinen 	mdelay(2);
1789960aa7cSTomi Valkeinen 
17924aac601SLaurent Pinchart 	r = dss_mgr_enable(&sdi->output);
1809960aa7cSTomi Valkeinen 	if (r)
1819960aa7cSTomi Valkeinen 		goto err_mgr_enable;
1829960aa7cSTomi Valkeinen 
18319b4200dSLaurent Pinchart 	return;
1849960aa7cSTomi Valkeinen 
1859960aa7cSTomi Valkeinen err_mgr_enable:
18624aac601SLaurent Pinchart 	dss_sdi_disable(sdi->dss);
1879960aa7cSTomi Valkeinen err_sdi_enable:
1889960aa7cSTomi Valkeinen err_set_dss_clock_div:
1899960aa7cSTomi Valkeinen err_calc_clock_div:
19024aac601SLaurent Pinchart 	dispc_runtime_put(sdi->dss->dispc);
1919960aa7cSTomi Valkeinen err_get_dispc:
19224aac601SLaurent Pinchart 	regulator_disable(sdi->vdds_sdi_reg);
1939960aa7cSTomi Valkeinen }
1949960aa7cSTomi Valkeinen 
1959960aa7cSTomi Valkeinen static void sdi_display_disable(struct omap_dss_device *dssdev)
1969960aa7cSTomi Valkeinen {
19724aac601SLaurent Pinchart 	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
1989960aa7cSTomi Valkeinen 
19924aac601SLaurent Pinchart 	dss_mgr_disable(&sdi->output);
2009960aa7cSTomi Valkeinen 
20124aac601SLaurent Pinchart 	dss_sdi_disable(sdi->dss);
2029960aa7cSTomi Valkeinen 
20324aac601SLaurent Pinchart 	dispc_runtime_put(sdi->dss->dispc);
20424aac601SLaurent Pinchart 
20524aac601SLaurent Pinchart 	regulator_disable(sdi->vdds_sdi_reg);
2069960aa7cSTomi Valkeinen }
2079960aa7cSTomi Valkeinen 
2089960aa7cSTomi Valkeinen static void sdi_set_timings(struct omap_dss_device *dssdev,
20941322aa6SLaurent Pinchart 			    const struct drm_display_mode *mode)
2109960aa7cSTomi Valkeinen {
21124aac601SLaurent Pinchart 	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
21224aac601SLaurent Pinchart 
213e5906f76SLaurent Pinchart 	sdi->pixelclock = mode->clock * 1000;
2149960aa7cSTomi Valkeinen }
2159960aa7cSTomi Valkeinen 
2169960aa7cSTomi Valkeinen static int sdi_check_timings(struct omap_dss_device *dssdev,
21741322aa6SLaurent Pinchart 			     struct drm_display_mode *mode)
2189960aa7cSTomi Valkeinen {
21996fc64c7SLaurent Pinchart 	struct sdi_device *sdi = dssdev_to_sdi(dssdev);
22096fc64c7SLaurent Pinchart 	struct dispc_clock_info dispc_cinfo;
22141322aa6SLaurent Pinchart 	unsigned long pixelclock = mode->clock * 1000;
22296fc64c7SLaurent Pinchart 	unsigned long fck;
22396fc64c7SLaurent Pinchart 	unsigned long pck;
22496fc64c7SLaurent Pinchart 	int r;
22596fc64c7SLaurent Pinchart 
22641322aa6SLaurent Pinchart 	if (pixelclock == 0)
2279960aa7cSTomi Valkeinen 		return -EINVAL;
2289960aa7cSTomi Valkeinen 
22941322aa6SLaurent Pinchart 	r = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo);
23096fc64c7SLaurent Pinchart 	if (r)
23196fc64c7SLaurent Pinchart 		return r;
23296fc64c7SLaurent Pinchart 
23396fc64c7SLaurent Pinchart 	pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div;
23496fc64c7SLaurent Pinchart 
23541322aa6SLaurent Pinchart 	if (pck != pixelclock) {
23696fc64c7SLaurent Pinchart 		DSSWARN("Pixel clock adjusted from %lu Hz to %lu Hz\n",
23741322aa6SLaurent Pinchart 			pixelclock, pck);
23896fc64c7SLaurent Pinchart 
23941322aa6SLaurent Pinchart 		mode->clock = pck / 1000;
24096fc64c7SLaurent Pinchart 	}
24196fc64c7SLaurent Pinchart 
2429960aa7cSTomi Valkeinen 	return 0;
2439960aa7cSTomi Valkeinen }
2449960aa7cSTomi Valkeinen 
245511afb44SLaurent Pinchart static int sdi_connect(struct omap_dss_device *src,
2469960aa7cSTomi Valkeinen 		       struct omap_dss_device *dst)
2479960aa7cSTomi Valkeinen {
248f8a8eabbSLaurent Pinchart 	return omapdss_device_connect(dst->dss, dst, dst->next);
24971316556SLaurent Pinchart }
25071316556SLaurent Pinchart 
251511afb44SLaurent Pinchart static void sdi_disconnect(struct omap_dss_device *src,
2529960aa7cSTomi Valkeinen 			   struct omap_dss_device *dst)
2539960aa7cSTomi Valkeinen {
254511afb44SLaurent Pinchart 	omapdss_device_disconnect(dst, dst->next);
2559960aa7cSTomi Valkeinen }
2569960aa7cSTomi Valkeinen 
257b93109d7SLaurent Pinchart static const struct omap_dss_device_ops sdi_ops = {
2589960aa7cSTomi Valkeinen 	.connect = sdi_connect,
2599960aa7cSTomi Valkeinen 	.disconnect = sdi_disconnect,
2609960aa7cSTomi Valkeinen 
2619960aa7cSTomi Valkeinen 	.enable = sdi_display_enable,
2629960aa7cSTomi Valkeinen 	.disable = sdi_display_disable,
2639960aa7cSTomi Valkeinen 
2649960aa7cSTomi Valkeinen 	.check_timings = sdi_check_timings,
2659960aa7cSTomi Valkeinen 	.set_timings = sdi_set_timings,
2669960aa7cSTomi Valkeinen };
2679960aa7cSTomi Valkeinen 
26827d62452SLaurent Pinchart static int sdi_init_output(struct sdi_device *sdi)
2699960aa7cSTomi Valkeinen {
27024aac601SLaurent Pinchart 	struct omap_dss_device *out = &sdi->output;
27171316556SLaurent Pinchart 	int r;
2729960aa7cSTomi Valkeinen 
27324aac601SLaurent Pinchart 	out->dev = &sdi->pdev->dev;
2749960aa7cSTomi Valkeinen 	out->id = OMAP_DSS_OUTPUT_SDI;
2750dbfc396SLaurent Pinchart 	out->type = OMAP_DISPLAY_TYPE_SDI;
2769960aa7cSTomi Valkeinen 	out->name = "sdi.0";
2779960aa7cSTomi Valkeinen 	out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
2789960aa7cSTomi Valkeinen 	/* We have SDI only on OMAP3, where it's on port 1 */
2794e20bda6SLaurent Pinchart 	out->of_ports = BIT(1);
280b93109d7SLaurent Pinchart 	out->ops = &sdi_ops;
2819960aa7cSTomi Valkeinen 	out->owner = THIS_MODULE;
282*88bc4178SLaurent Pinchart 	out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE	/* 15.5.9.1.2 */
283*88bc4178SLaurent Pinchart 		       | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
2849960aa7cSTomi Valkeinen 
285d17eb453SLaurent Pinchart 	r = omapdss_device_init_output(out);
286d17eb453SLaurent Pinchart 	if (r < 0)
28771316556SLaurent Pinchart 		return r;
28871316556SLaurent Pinchart 
289de57e9dbSLaurent Pinchart 	omapdss_device_register(out);
29027d62452SLaurent Pinchart 
29127d62452SLaurent Pinchart 	return 0;
2929960aa7cSTomi Valkeinen }
2939960aa7cSTomi Valkeinen 
29424aac601SLaurent Pinchart static void sdi_uninit_output(struct sdi_device *sdi)
2959960aa7cSTomi Valkeinen {
296de57e9dbSLaurent Pinchart 	omapdss_device_unregister(&sdi->output);
297d17eb453SLaurent Pinchart 	omapdss_device_cleanup_output(&sdi->output);
2989960aa7cSTomi Valkeinen }
2999960aa7cSTomi Valkeinen 
300d7157dfeSLaurent Pinchart int sdi_init_port(struct dss_device *dss, struct platform_device *pdev,
301d7157dfeSLaurent Pinchart 		  struct device_node *port)
3029960aa7cSTomi Valkeinen {
30324aac601SLaurent Pinchart 	struct sdi_device *sdi;
3049960aa7cSTomi Valkeinen 	struct device_node *ep;
3059960aa7cSTomi Valkeinen 	u32 datapairs;
3069960aa7cSTomi Valkeinen 	int r;
3079960aa7cSTomi Valkeinen 
30824aac601SLaurent Pinchart 	sdi = kzalloc(sizeof(*sdi), GFP_KERNEL);
30924aac601SLaurent Pinchart 	if (!sdi)
31024aac601SLaurent Pinchart 		return -ENOMEM;
31124aac601SLaurent Pinchart 
31209bffa6eSRob Herring 	ep = of_get_next_child(port, NULL);
31324aac601SLaurent Pinchart 	if (!ep) {
31424aac601SLaurent Pinchart 		r = 0;
31524aac601SLaurent Pinchart 		goto err_free;
31624aac601SLaurent Pinchart 	}
3179960aa7cSTomi Valkeinen 
3189960aa7cSTomi Valkeinen 	r = of_property_read_u32(ep, "datapairs", &datapairs);
31966aacfe2SLaurent Pinchart 	of_node_put(ep);
3209960aa7cSTomi Valkeinen 	if (r) {
3219960aa7cSTomi Valkeinen 		DSSERR("failed to parse datapairs\n");
32266aacfe2SLaurent Pinchart 		goto err_free;
3239960aa7cSTomi Valkeinen 	}
3249960aa7cSTomi Valkeinen 
32524aac601SLaurent Pinchart 	sdi->datapairs = datapairs;
32624aac601SLaurent Pinchart 	sdi->dss = dss;
3279960aa7cSTomi Valkeinen 
32824aac601SLaurent Pinchart 	sdi->pdev = pdev;
32924aac601SLaurent Pinchart 	port->data = sdi;
3309960aa7cSTomi Valkeinen 
3318a36357aSLaurent Pinchart 	sdi->vdds_sdi_reg = devm_regulator_get(&pdev->dev, "vdds_sdi");
3328a36357aSLaurent Pinchart 	if (IS_ERR(sdi->vdds_sdi_reg)) {
3338a36357aSLaurent Pinchart 		r = PTR_ERR(sdi->vdds_sdi_reg);
3348a36357aSLaurent Pinchart 		if (r != -EPROBE_DEFER)
3358a36357aSLaurent Pinchart 			DSSERR("can't get VDDS_SDI regulator\n");
3368a36357aSLaurent Pinchart 		goto err_free;
3378a36357aSLaurent Pinchart 	}
3388a36357aSLaurent Pinchart 
33927d62452SLaurent Pinchart 	r = sdi_init_output(sdi);
34027d62452SLaurent Pinchart 	if (r)
34127d62452SLaurent Pinchart 		goto err_free;
3429960aa7cSTomi Valkeinen 
3439960aa7cSTomi Valkeinen 	return 0;
3449960aa7cSTomi Valkeinen 
34524aac601SLaurent Pinchart err_free:
34624aac601SLaurent Pinchart 	kfree(sdi);
3479960aa7cSTomi Valkeinen 
3489960aa7cSTomi Valkeinen 	return r;
3499960aa7cSTomi Valkeinen }
3509960aa7cSTomi Valkeinen 
3519960aa7cSTomi Valkeinen void sdi_uninit_port(struct device_node *port)
3529960aa7cSTomi Valkeinen {
35324aac601SLaurent Pinchart 	struct sdi_device *sdi = port->data;
35424aac601SLaurent Pinchart 
35524aac601SLaurent Pinchart 	if (!sdi)
3569960aa7cSTomi Valkeinen 		return;
3579960aa7cSTomi Valkeinen 
35824aac601SLaurent Pinchart 	sdi_uninit_output(sdi);
35924aac601SLaurent Pinchart 	kfree(sdi);
3609960aa7cSTomi Valkeinen }
361