1 /* 2 * Copyright (C) 2009 Nokia Corporation 3 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 4 * 5 * Some code and ideas taken from drivers/video/omap/ driver 6 * by Imre Deak. 7 * 8 * This program is free software; you can redistribute it and/or modify it 9 * under the terms of the GNU General Public License version 2 as published by 10 * the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 * more details. 16 * 17 * You should have received a copy of the GNU General Public License along with 18 * this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 #define DSS_SUBSYS_NAME "DPI" 22 23 #include <linux/kernel.h> 24 #include <linux/delay.h> 25 #include <linux/export.h> 26 #include <linux/err.h> 27 #include <linux/errno.h> 28 #include <linux/platform_device.h> 29 #include <linux/regulator/consumer.h> 30 #include <linux/string.h> 31 #include <linux/of.h> 32 #include <linux/clk.h> 33 #include <linux/sys_soc.h> 34 35 #include "omapdss.h" 36 #include "dss.h" 37 38 struct dpi_data { 39 struct platform_device *pdev; 40 enum dss_model dss_model; 41 struct dss_device *dss; 42 unsigned int id; 43 44 struct regulator *vdds_dsi_reg; 45 enum dss_clk_source clk_src; 46 struct dss_pll *pll; 47 48 struct mutex lock; 49 50 struct videomode vm; 51 struct dss_lcd_mgr_config mgr_config; 52 int data_lines; 53 54 struct omap_dss_device output; 55 }; 56 57 static struct dpi_data *dpi_get_data_from_dssdev(struct omap_dss_device *dssdev) 58 { 59 return container_of(dssdev, struct dpi_data, output); 60 } 61 62 static enum dss_clk_source dpi_get_clk_src_dra7xx(struct dpi_data *dpi, 63 enum omap_channel channel) 64 { 65 /* 66 * Possible clock sources: 67 * LCD1: FCK/PLL1_1/HDMI_PLL 68 * LCD2: FCK/PLL1_3/HDMI_PLL (DRA74x: PLL2_3) 69 * LCD3: FCK/PLL1_3/HDMI_PLL (DRA74x: PLL2_1) 70 */ 71 72 switch (channel) { 73 case OMAP_DSS_CHANNEL_LCD: 74 { 75 if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL1_1)) 76 return DSS_CLK_SRC_PLL1_1; 77 break; 78 } 79 case OMAP_DSS_CHANNEL_LCD2: 80 { 81 if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL1_3)) 82 return DSS_CLK_SRC_PLL1_3; 83 if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL2_3)) 84 return DSS_CLK_SRC_PLL2_3; 85 break; 86 } 87 case OMAP_DSS_CHANNEL_LCD3: 88 { 89 if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL2_1)) 90 return DSS_CLK_SRC_PLL2_1; 91 if (dss_pll_find_by_src(dpi->dss, DSS_CLK_SRC_PLL1_3)) 92 return DSS_CLK_SRC_PLL1_3; 93 break; 94 } 95 default: 96 break; 97 } 98 99 return DSS_CLK_SRC_FCK; 100 } 101 102 static enum dss_clk_source dpi_get_clk_src(struct dpi_data *dpi) 103 { 104 enum omap_channel channel = dpi->output.dispc_channel; 105 106 /* 107 * XXX we can't currently use DSI PLL for DPI with OMAP3, as the DSI PLL 108 * would also be used for DISPC fclk. Meaning, when the DPI output is 109 * disabled, DISPC clock will be disabled, and TV out will stop. 110 */ 111 switch (dpi->dss_model) { 112 case DSS_MODEL_OMAP2: 113 case DSS_MODEL_OMAP3: 114 return DSS_CLK_SRC_FCK; 115 116 case DSS_MODEL_OMAP4: 117 switch (channel) { 118 case OMAP_DSS_CHANNEL_LCD: 119 return DSS_CLK_SRC_PLL1_1; 120 case OMAP_DSS_CHANNEL_LCD2: 121 return DSS_CLK_SRC_PLL2_1; 122 default: 123 return DSS_CLK_SRC_FCK; 124 } 125 126 case DSS_MODEL_OMAP5: 127 switch (channel) { 128 case OMAP_DSS_CHANNEL_LCD: 129 return DSS_CLK_SRC_PLL1_1; 130 case OMAP_DSS_CHANNEL_LCD3: 131 return DSS_CLK_SRC_PLL2_1; 132 case OMAP_DSS_CHANNEL_LCD2: 133 default: 134 return DSS_CLK_SRC_FCK; 135 } 136 137 case DSS_MODEL_DRA7: 138 return dpi_get_clk_src_dra7xx(dpi, channel); 139 140 default: 141 return DSS_CLK_SRC_FCK; 142 } 143 } 144 145 struct dpi_clk_calc_ctx { 146 struct dpi_data *dpi; 147 unsigned int clkout_idx; 148 149 /* inputs */ 150 151 unsigned long pck_min, pck_max; 152 153 /* outputs */ 154 155 struct dss_pll_clock_info pll_cinfo; 156 unsigned long fck; 157 struct dispc_clock_info dispc_cinfo; 158 }; 159 160 static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck, 161 unsigned long pck, void *data) 162 { 163 struct dpi_clk_calc_ctx *ctx = data; 164 165 /* 166 * Odd dividers give us uneven duty cycle, causing problem when level 167 * shifted. So skip all odd dividers when the pixel clock is on the 168 * higher side. 169 */ 170 if (ctx->pck_min >= 100000000) { 171 if (lckd > 1 && lckd % 2 != 0) 172 return false; 173 174 if (pckd > 1 && pckd % 2 != 0) 175 return false; 176 } 177 178 ctx->dispc_cinfo.lck_div = lckd; 179 ctx->dispc_cinfo.pck_div = pckd; 180 ctx->dispc_cinfo.lck = lck; 181 ctx->dispc_cinfo.pck = pck; 182 183 return true; 184 } 185 186 187 static bool dpi_calc_hsdiv_cb(int m_dispc, unsigned long dispc, 188 void *data) 189 { 190 struct dpi_clk_calc_ctx *ctx = data; 191 192 ctx->pll_cinfo.mX[ctx->clkout_idx] = m_dispc; 193 ctx->pll_cinfo.clkout[ctx->clkout_idx] = dispc; 194 195 return dispc_div_calc(ctx->dpi->dss->dispc, dispc, 196 ctx->pck_min, ctx->pck_max, 197 dpi_calc_dispc_cb, ctx); 198 } 199 200 201 static bool dpi_calc_pll_cb(int n, int m, unsigned long fint, 202 unsigned long clkdco, 203 void *data) 204 { 205 struct dpi_clk_calc_ctx *ctx = data; 206 207 ctx->pll_cinfo.n = n; 208 ctx->pll_cinfo.m = m; 209 ctx->pll_cinfo.fint = fint; 210 ctx->pll_cinfo.clkdco = clkdco; 211 212 return dss_pll_hsdiv_calc_a(ctx->dpi->pll, clkdco, 213 ctx->pck_min, dss_get_max_fck_rate(ctx->dpi->dss), 214 dpi_calc_hsdiv_cb, ctx); 215 } 216 217 static bool dpi_calc_dss_cb(unsigned long fck, void *data) 218 { 219 struct dpi_clk_calc_ctx *ctx = data; 220 221 ctx->fck = fck; 222 223 return dispc_div_calc(ctx->dpi->dss->dispc, fck, 224 ctx->pck_min, ctx->pck_max, 225 dpi_calc_dispc_cb, ctx); 226 } 227 228 static bool dpi_pll_clk_calc(struct dpi_data *dpi, unsigned long pck, 229 struct dpi_clk_calc_ctx *ctx) 230 { 231 unsigned long clkin; 232 233 memset(ctx, 0, sizeof(*ctx)); 234 ctx->dpi = dpi; 235 ctx->clkout_idx = dss_pll_get_clkout_idx_for_src(dpi->clk_src); 236 237 clkin = clk_get_rate(dpi->pll->clkin); 238 239 if (dpi->pll->hw->type == DSS_PLL_TYPE_A) { 240 unsigned long pll_min, pll_max; 241 242 ctx->pck_min = pck - 1000; 243 ctx->pck_max = pck + 1000; 244 245 pll_min = 0; 246 pll_max = 0; 247 248 return dss_pll_calc_a(ctx->dpi->pll, clkin, 249 pll_min, pll_max, 250 dpi_calc_pll_cb, ctx); 251 } else { /* DSS_PLL_TYPE_B */ 252 dss_pll_calc_b(dpi->pll, clkin, pck, &ctx->pll_cinfo); 253 254 ctx->dispc_cinfo.lck_div = 1; 255 ctx->dispc_cinfo.pck_div = 1; 256 ctx->dispc_cinfo.lck = ctx->pll_cinfo.clkout[0]; 257 ctx->dispc_cinfo.pck = ctx->dispc_cinfo.lck; 258 259 return true; 260 } 261 } 262 263 static bool dpi_dss_clk_calc(struct dpi_data *dpi, unsigned long pck, 264 struct dpi_clk_calc_ctx *ctx) 265 { 266 int i; 267 268 /* 269 * DSS fck gives us very few possibilities, so finding a good pixel 270 * clock may not be possible. We try multiple times to find the clock, 271 * each time widening the pixel clock range we look for, up to 272 * +/- ~15MHz. 273 */ 274 275 for (i = 0; i < 25; ++i) { 276 bool ok; 277 278 memset(ctx, 0, sizeof(*ctx)); 279 ctx->dpi = dpi; 280 if (pck > 1000 * i * i * i) 281 ctx->pck_min = max(pck - 1000 * i * i * i, 0lu); 282 else 283 ctx->pck_min = 0; 284 ctx->pck_max = pck + 1000 * i * i * i; 285 286 ok = dss_div_calc(dpi->dss, pck, ctx->pck_min, 287 dpi_calc_dss_cb, ctx); 288 if (ok) 289 return ok; 290 } 291 292 return false; 293 } 294 295 296 297 static int dpi_set_pll_clk(struct dpi_data *dpi, enum omap_channel channel, 298 unsigned long pck_req, unsigned long *fck, int *lck_div, 299 int *pck_div) 300 { 301 struct dpi_clk_calc_ctx ctx; 302 int r; 303 bool ok; 304 305 ok = dpi_pll_clk_calc(dpi, pck_req, &ctx); 306 if (!ok) 307 return -EINVAL; 308 309 r = dss_pll_set_config(dpi->pll, &ctx.pll_cinfo); 310 if (r) 311 return r; 312 313 dss_select_lcd_clk_source(dpi->dss, channel, dpi->clk_src); 314 315 dpi->mgr_config.clock_info = ctx.dispc_cinfo; 316 317 *fck = ctx.pll_cinfo.clkout[ctx.clkout_idx]; 318 *lck_div = ctx.dispc_cinfo.lck_div; 319 *pck_div = ctx.dispc_cinfo.pck_div; 320 321 return 0; 322 } 323 324 static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req, 325 unsigned long *fck, int *lck_div, int *pck_div) 326 { 327 struct dpi_clk_calc_ctx ctx; 328 int r; 329 bool ok; 330 331 ok = dpi_dss_clk_calc(dpi, pck_req, &ctx); 332 if (!ok) 333 return -EINVAL; 334 335 r = dss_set_fck_rate(dpi->dss, ctx.fck); 336 if (r) 337 return r; 338 339 dpi->mgr_config.clock_info = ctx.dispc_cinfo; 340 341 *fck = ctx.fck; 342 *lck_div = ctx.dispc_cinfo.lck_div; 343 *pck_div = ctx.dispc_cinfo.pck_div; 344 345 return 0; 346 } 347 348 static int dpi_set_mode(struct dpi_data *dpi) 349 { 350 const struct videomode *vm = &dpi->vm; 351 int lck_div = 0, pck_div = 0; 352 unsigned long fck = 0; 353 int r = 0; 354 355 if (dpi->pll) 356 r = dpi_set_pll_clk(dpi, dpi->output.dispc_channel, 357 vm->pixelclock, &fck, &lck_div, &pck_div); 358 else 359 r = dpi_set_dispc_clk(dpi, vm->pixelclock, &fck, 360 &lck_div, &pck_div); 361 if (r) 362 return r; 363 364 return 0; 365 } 366 367 static void dpi_config_lcd_manager(struct dpi_data *dpi) 368 { 369 dpi->mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS; 370 371 dpi->mgr_config.stallmode = false; 372 dpi->mgr_config.fifohandcheck = false; 373 374 dpi->mgr_config.video_port_width = dpi->data_lines; 375 376 dpi->mgr_config.lcden_sig_polarity = 0; 377 378 dss_mgr_set_lcd_config(&dpi->output, &dpi->mgr_config); 379 } 380 381 static int dpi_display_enable(struct omap_dss_device *dssdev) 382 { 383 struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); 384 struct omap_dss_device *out = &dpi->output; 385 int r; 386 387 mutex_lock(&dpi->lock); 388 389 if (!out->dispc_channel_connected) { 390 DSSERR("failed to enable display: no output/manager\n"); 391 r = -ENODEV; 392 goto err_no_out_mgr; 393 } 394 395 if (dpi->vdds_dsi_reg) { 396 r = regulator_enable(dpi->vdds_dsi_reg); 397 if (r) 398 goto err_reg_enable; 399 } 400 401 r = dispc_runtime_get(dpi->dss->dispc); 402 if (r) 403 goto err_get_dispc; 404 405 r = dss_dpi_select_source(dpi->dss, dpi->id, out->dispc_channel); 406 if (r) 407 goto err_src_sel; 408 409 if (dpi->pll) { 410 r = dss_pll_enable(dpi->pll); 411 if (r) 412 goto err_pll_init; 413 } 414 415 r = dpi_set_mode(dpi); 416 if (r) 417 goto err_set_mode; 418 419 dpi_config_lcd_manager(dpi); 420 421 mdelay(2); 422 423 r = dss_mgr_enable(&dpi->output); 424 if (r) 425 goto err_mgr_enable; 426 427 mutex_unlock(&dpi->lock); 428 429 return 0; 430 431 err_mgr_enable: 432 err_set_mode: 433 if (dpi->pll) 434 dss_pll_disable(dpi->pll); 435 err_pll_init: 436 err_src_sel: 437 dispc_runtime_put(dpi->dss->dispc); 438 err_get_dispc: 439 if (dpi->vdds_dsi_reg) 440 regulator_disable(dpi->vdds_dsi_reg); 441 err_reg_enable: 442 err_no_out_mgr: 443 mutex_unlock(&dpi->lock); 444 return r; 445 } 446 447 static void dpi_display_disable(struct omap_dss_device *dssdev) 448 { 449 struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); 450 451 mutex_lock(&dpi->lock); 452 453 dss_mgr_disable(&dpi->output); 454 455 if (dpi->pll) { 456 dss_select_lcd_clk_source(dpi->dss, dpi->output.dispc_channel, 457 DSS_CLK_SRC_FCK); 458 dss_pll_disable(dpi->pll); 459 } 460 461 dispc_runtime_put(dpi->dss->dispc); 462 463 if (dpi->vdds_dsi_reg) 464 regulator_disable(dpi->vdds_dsi_reg); 465 466 mutex_unlock(&dpi->lock); 467 } 468 469 static void dpi_set_timings(struct omap_dss_device *dssdev, 470 const struct videomode *vm) 471 { 472 struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); 473 474 DSSDBG("dpi_set_timings\n"); 475 476 mutex_lock(&dpi->lock); 477 478 dpi->vm = *vm; 479 480 mutex_unlock(&dpi->lock); 481 } 482 483 static int dpi_check_timings(struct omap_dss_device *dssdev, 484 struct videomode *vm) 485 { 486 struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev); 487 int lck_div, pck_div; 488 unsigned long fck; 489 unsigned long pck; 490 struct dpi_clk_calc_ctx ctx; 491 bool ok; 492 493 if (vm->hactive % 8 != 0) 494 return -EINVAL; 495 496 if (vm->pixelclock == 0) 497 return -EINVAL; 498 499 if (dpi->pll) { 500 ok = dpi_pll_clk_calc(dpi, vm->pixelclock, &ctx); 501 if (!ok) 502 return -EINVAL; 503 504 fck = ctx.pll_cinfo.clkout[ctx.clkout_idx]; 505 } else { 506 ok = dpi_dss_clk_calc(dpi, vm->pixelclock, &ctx); 507 if (!ok) 508 return -EINVAL; 509 510 fck = ctx.fck; 511 } 512 513 lck_div = ctx.dispc_cinfo.lck_div; 514 pck_div = ctx.dispc_cinfo.pck_div; 515 516 pck = fck / lck_div / pck_div; 517 518 vm->pixelclock = pck; 519 520 return 0; 521 } 522 523 static int dpi_verify_pll(struct dss_pll *pll) 524 { 525 int r; 526 527 /* do initial setup with the PLL to see if it is operational */ 528 529 r = dss_pll_enable(pll); 530 if (r) 531 return r; 532 533 dss_pll_disable(pll); 534 535 return 0; 536 } 537 538 static void dpi_init_pll(struct dpi_data *dpi) 539 { 540 struct dss_pll *pll; 541 542 if (dpi->pll) 543 return; 544 545 dpi->clk_src = dpi_get_clk_src(dpi); 546 547 pll = dss_pll_find_by_src(dpi->dss, dpi->clk_src); 548 if (!pll) 549 return; 550 551 if (dpi_verify_pll(pll)) { 552 DSSWARN("PLL not operational\n"); 553 return; 554 } 555 556 dpi->pll = pll; 557 } 558 559 /* 560 * Return a hardcoded channel for the DPI output. This should work for 561 * current use cases, but this can be later expanded to either resolve 562 * the channel in some more dynamic manner, or get the channel as a user 563 * parameter. 564 */ 565 static enum omap_channel dpi_get_channel(struct dpi_data *dpi) 566 { 567 switch (dpi->dss_model) { 568 case DSS_MODEL_OMAP2: 569 case DSS_MODEL_OMAP3: 570 return OMAP_DSS_CHANNEL_LCD; 571 572 case DSS_MODEL_DRA7: 573 switch (dpi->id) { 574 case 2: 575 return OMAP_DSS_CHANNEL_LCD3; 576 case 1: 577 return OMAP_DSS_CHANNEL_LCD2; 578 case 0: 579 default: 580 return OMAP_DSS_CHANNEL_LCD; 581 } 582 583 case DSS_MODEL_OMAP4: 584 return OMAP_DSS_CHANNEL_LCD2; 585 586 case DSS_MODEL_OMAP5: 587 return OMAP_DSS_CHANNEL_LCD3; 588 589 default: 590 DSSWARN("unsupported DSS version\n"); 591 return OMAP_DSS_CHANNEL_LCD; 592 } 593 } 594 595 static int dpi_connect(struct omap_dss_device *src, 596 struct omap_dss_device *dst) 597 { 598 struct dpi_data *dpi = dpi_get_data_from_dssdev(dst); 599 int r; 600 601 dpi_init_pll(dpi); 602 603 r = omapdss_device_connect(dst->dss, dst, dst->next); 604 if (r) 605 return r; 606 607 dst->dispc_channel_connected = true; 608 return 0; 609 } 610 611 static void dpi_disconnect(struct omap_dss_device *src, 612 struct omap_dss_device *dst) 613 { 614 dst->dispc_channel_connected = false; 615 616 omapdss_device_disconnect(dst, dst->next); 617 } 618 619 static const struct omap_dss_device_ops dpi_ops = { 620 .connect = dpi_connect, 621 .disconnect = dpi_disconnect, 622 623 .enable = dpi_display_enable, 624 .disable = dpi_display_disable, 625 626 .check_timings = dpi_check_timings, 627 .set_timings = dpi_set_timings, 628 }; 629 630 static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port) 631 { 632 struct omap_dss_device *out = &dpi->output; 633 u32 port_num = 0; 634 int r; 635 636 of_property_read_u32(port, "reg", &port_num); 637 dpi->id = port_num <= 2 ? port_num : 0; 638 639 switch (port_num) { 640 case 2: 641 out->name = "dpi.2"; 642 break; 643 case 1: 644 out->name = "dpi.1"; 645 break; 646 case 0: 647 default: 648 out->name = "dpi.0"; 649 break; 650 } 651 652 out->dev = &dpi->pdev->dev; 653 out->id = OMAP_DSS_OUTPUT_DPI; 654 out->output_type = OMAP_DISPLAY_TYPE_DPI; 655 out->dispc_channel = dpi_get_channel(dpi); 656 out->of_ports = BIT(port_num); 657 out->ops = &dpi_ops; 658 out->owner = THIS_MODULE; 659 660 out->next = omapdss_of_find_connected_device(out->dev->of_node, 0); 661 if (IS_ERR(out->next)) { 662 if (PTR_ERR(out->next) != -EPROBE_DEFER) 663 dev_err(out->dev, "failed to find video sink\n"); 664 return PTR_ERR(out->next); 665 } 666 667 r = omapdss_output_validate(out); 668 if (r) { 669 omapdss_device_put(out->next); 670 out->next = NULL; 671 return r; 672 } 673 674 omapdss_device_register(out); 675 676 return 0; 677 } 678 679 static void dpi_uninit_output_port(struct device_node *port) 680 { 681 struct dpi_data *dpi = port->data; 682 struct omap_dss_device *out = &dpi->output; 683 684 if (out->next) 685 omapdss_device_put(out->next); 686 omapdss_device_unregister(out); 687 } 688 689 static const struct soc_device_attribute dpi_soc_devices[] = { 690 { .machine = "OMAP3[456]*" }, 691 { .machine = "[AD]M37*" }, 692 { /* sentinel */ } 693 }; 694 695 static int dpi_init_regulator(struct dpi_data *dpi) 696 { 697 struct regulator *vdds_dsi; 698 699 /* 700 * The DPI uses the DSI VDDS on OMAP34xx, OMAP35xx, OMAP36xx, AM37xx and 701 * DM37xx only. 702 */ 703 if (!soc_device_match(dpi_soc_devices)) 704 return 0; 705 706 vdds_dsi = devm_regulator_get(&dpi->pdev->dev, "vdds_dsi"); 707 if (IS_ERR(vdds_dsi)) { 708 if (PTR_ERR(vdds_dsi) != -EPROBE_DEFER) 709 DSSERR("can't get VDDS_DSI regulator\n"); 710 return PTR_ERR(vdds_dsi); 711 } 712 713 dpi->vdds_dsi_reg = vdds_dsi; 714 715 return 0; 716 } 717 718 int dpi_init_port(struct dss_device *dss, struct platform_device *pdev, 719 struct device_node *port, enum dss_model dss_model) 720 { 721 struct dpi_data *dpi; 722 struct device_node *ep; 723 u32 datalines; 724 int r; 725 726 dpi = devm_kzalloc(&pdev->dev, sizeof(*dpi), GFP_KERNEL); 727 if (!dpi) 728 return -ENOMEM; 729 730 ep = of_get_next_child(port, NULL); 731 if (!ep) 732 return 0; 733 734 r = of_property_read_u32(ep, "data-lines", &datalines); 735 of_node_put(ep); 736 if (r) { 737 DSSERR("failed to parse datalines\n"); 738 return r; 739 } 740 741 dpi->data_lines = datalines; 742 743 dpi->pdev = pdev; 744 dpi->dss_model = dss_model; 745 dpi->dss = dss; 746 port->data = dpi; 747 748 mutex_init(&dpi->lock); 749 750 r = dpi_init_regulator(dpi); 751 if (r) 752 return r; 753 754 return dpi_init_output_port(dpi, port); 755 } 756 757 void dpi_uninit_port(struct device_node *port) 758 { 759 struct dpi_data *dpi = port->data; 760 761 if (!dpi) 762 return; 763 764 dpi_uninit_output_port(port); 765 } 766