1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * R-Car Gen3 HDMI PHY 4 * 5 * Copyright (C) 2016 Renesas Electronics Corporation 6 * 7 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) 8 */ 9 10 #include <linux/mod_devicetable.h> 11 #include <linux/module.h> 12 #include <linux/platform_device.h> 13 14 #include <drm/bridge/dw_hdmi.h> 15 #include <drm/drm_modes.h> 16 17 #define RCAR_HDMI_PHY_OPMODE_PLLCFG 0x06 /* Mode of operation and PLL dividers */ 18 #define RCAR_HDMI_PHY_PLLCURRGMPCTRL 0x10 /* PLL current and Gmp (conductance) */ 19 #define RCAR_HDMI_PHY_PLLDIVCTRL 0x11 /* PLL dividers */ 20 21 struct rcar_hdmi_phy_params { 22 unsigned long mpixelclock; 23 u16 opmode_div; /* Mode of operation and PLL dividers */ 24 u16 curr_gmp; /* PLL current and Gmp (conductance) */ 25 u16 div; /* PLL dividers */ 26 }; 27 28 static const struct rcar_hdmi_phy_params rcar_hdmi_phy_params[] = { 29 { 35500000, 0x0003, 0x0344, 0x0328 }, 30 { 44900000, 0x0003, 0x0285, 0x0128 }, 31 { 71000000, 0x0002, 0x1184, 0x0314 }, 32 { 90000000, 0x0002, 0x1144, 0x0114 }, 33 { 140250000, 0x0001, 0x20c4, 0x030a }, 34 { 182750000, 0x0001, 0x2084, 0x010a }, 35 { 281250000, 0x0000, 0x0084, 0x0305 }, 36 { 297000000, 0x0000, 0x0084, 0x0105 }, 37 { ~0UL, 0x0000, 0x0000, 0x0000 }, 38 }; 39 40 static enum drm_mode_status 41 rcar_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, 42 const struct drm_display_info *info, 43 const struct drm_display_mode *mode) 44 { 45 /* 46 * The maximum supported clock frequency is 297 MHz, as shown in the PHY 47 * parameters table. 48 */ 49 if (mode->clock > 297000) 50 return MODE_CLOCK_HIGH; 51 52 return MODE_OK; 53 } 54 55 static int rcar_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data, 56 unsigned long mpixelclock) 57 { 58 const struct rcar_hdmi_phy_params *params = rcar_hdmi_phy_params; 59 60 for (; params->mpixelclock != ~0UL; ++params) { 61 if (mpixelclock <= params->mpixelclock) 62 break; 63 } 64 65 if (params->mpixelclock == ~0UL) 66 return -EINVAL; 67 68 dw_hdmi_phy_i2c_write(hdmi, params->opmode_div, 69 RCAR_HDMI_PHY_OPMODE_PLLCFG); 70 dw_hdmi_phy_i2c_write(hdmi, params->curr_gmp, 71 RCAR_HDMI_PHY_PLLCURRGMPCTRL); 72 dw_hdmi_phy_i2c_write(hdmi, params->div, RCAR_HDMI_PHY_PLLDIVCTRL); 73 74 return 0; 75 } 76 77 static const struct dw_hdmi_plat_data rcar_dw_hdmi_plat_data = { 78 .output_port = 1, 79 .mode_valid = rcar_hdmi_mode_valid, 80 .configure_phy = rcar_hdmi_phy_configure, 81 }; 82 83 static int rcar_dw_hdmi_probe(struct platform_device *pdev) 84 { 85 struct dw_hdmi *hdmi; 86 87 hdmi = dw_hdmi_probe(pdev, &rcar_dw_hdmi_plat_data); 88 if (IS_ERR(hdmi)) 89 return PTR_ERR(hdmi); 90 91 platform_set_drvdata(pdev, hdmi); 92 93 return 0; 94 } 95 96 static void rcar_dw_hdmi_remove(struct platform_device *pdev) 97 { 98 struct dw_hdmi *hdmi = platform_get_drvdata(pdev); 99 100 dw_hdmi_remove(hdmi); 101 } 102 103 static const struct of_device_id rcar_dw_hdmi_of_table[] = { 104 { .compatible = "renesas,rcar-gen3-hdmi" }, 105 { /* Sentinel */ }, 106 }; 107 MODULE_DEVICE_TABLE(of, rcar_dw_hdmi_of_table); 108 109 static struct platform_driver rcar_dw_hdmi_platform_driver = { 110 .probe = rcar_dw_hdmi_probe, 111 .remove_new = rcar_dw_hdmi_remove, 112 .driver = { 113 .name = "rcar-dw-hdmi", 114 .of_match_table = rcar_dw_hdmi_of_table, 115 }, 116 }; 117 118 module_platform_driver(rcar_dw_hdmi_platform_driver); 119 120 MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); 121 MODULE_DESCRIPTION("Renesas R-Car Gen3 HDMI Encoder Driver"); 122 MODULE_LICENSE("GPL"); 123