1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (c) 2016 Allwinnertech Co., Ltd. 4 * Copyright (C) 2017-2018 Bootlin 5 * 6 * Maxime Ripard <maxime.ripard@free-electrons.com> 7 */ 8 9 #include <linux/bitops.h> 10 #include <linux/clk.h> 11 #include <linux/module.h> 12 #include <linux/of_address.h> 13 #include <linux/platform_device.h> 14 #include <linux/regmap.h> 15 #include <linux/reset.h> 16 17 #include <linux/phy/phy.h> 18 #include <linux/phy/phy-mipi-dphy.h> 19 20 #define SUN6I_DPHY_GCTL_REG 0x00 21 #define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) 22 #define SUN6I_DPHY_GCTL_EN BIT(0) 23 24 #define SUN6I_DPHY_TX_CTL_REG 0x04 25 #define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28) 26 27 #define SUN6I_DPHY_RX_CTL_REG 0x08 28 #define SUN6I_DPHY_RX_CTL_EN_DBC BIT(31) 29 #define SUN6I_DPHY_RX_CTL_RX_CLK_FORCE BIT(24) 30 #define SUN6I_DPHY_RX_CTL_RX_D3_FORCE BIT(23) 31 #define SUN6I_DPHY_RX_CTL_RX_D2_FORCE BIT(22) 32 #define SUN6I_DPHY_RX_CTL_RX_D1_FORCE BIT(21) 33 #define SUN6I_DPHY_RX_CTL_RX_D0_FORCE BIT(20) 34 35 #define SUN6I_DPHY_TX_TIME0_REG 0x10 36 #define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24) 37 #define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16) 38 #define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff) 39 40 #define SUN6I_DPHY_TX_TIME1_REG 0x14 41 #define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24) 42 #define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16) 43 #define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8) 44 #define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff) 45 46 #define SUN6I_DPHY_TX_TIME2_REG 0x18 47 #define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff) 48 49 #define SUN6I_DPHY_TX_TIME3_REG 0x1c 50 51 #define SUN6I_DPHY_TX_TIME4_REG 0x20 52 #define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8) 53 #define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff) 54 55 #define SUN6I_DPHY_RX_TIME0_REG 0x30 56 #define SUN6I_DPHY_RX_TIME0_HS_RX_SYNC(n) (((n) & 0xff) << 24) 57 #define SUN6I_DPHY_RX_TIME0_HS_RX_CLK_MISS(n) (((n) & 0xff) << 16) 58 #define SUN6I_DPHY_RX_TIME0_LP_RX(n) (((n) & 0xff) << 8) 59 60 #define SUN6I_DPHY_RX_TIME1_REG 0x34 61 #define SUN6I_DPHY_RX_TIME1_RX_DLY(n) (((n) & 0xfff) << 20) 62 #define SUN6I_DPHY_RX_TIME1_LP_RX_ULPS_WP(n) ((n) & 0xfffff) 63 64 #define SUN6I_DPHY_RX_TIME2_REG 0x38 65 #define SUN6I_DPHY_RX_TIME2_HS_RX_ANA1(n) (((n) & 0xff) << 8) 66 #define SUN6I_DPHY_RX_TIME2_HS_RX_ANA0(n) ((n) & 0xff) 67 68 #define SUN6I_DPHY_RX_TIME3_REG 0x40 69 #define SUN6I_DPHY_RX_TIME3_LPRST_DLY(n) (((n) & 0xffff) << 16) 70 71 #define SUN6I_DPHY_ANA0_REG 0x4c 72 #define SUN6I_DPHY_ANA0_REG_PWS BIT(31) 73 #define SUN6I_DPHY_ANA0_REG_DMPC BIT(28) 74 #define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24) 75 #define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12) 76 #define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8) 77 #define SUN6I_DPHY_ANA0_REG_SFB(n) (((n) & 3) << 2) 78 79 #define SUN6I_DPHY_ANA1_REG 0x50 80 #define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31) 81 #define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28) 82 #define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24) 83 84 #define SUN6I_DPHY_ANA2_REG 0x54 85 #define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24) 86 #define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24) 87 #define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4) 88 #define SUN6I_DPHY_ANA2_REG_ENIB BIT(1) 89 90 #define SUN6I_DPHY_ANA3_REG 0x58 91 #define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28) 92 #define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28) 93 #define SUN6I_DPHY_ANA3_EN_VTTC BIT(27) 94 #define SUN6I_DPHY_ANA3_EN_DIV BIT(26) 95 #define SUN6I_DPHY_ANA3_EN_LDOC BIT(25) 96 #define SUN6I_DPHY_ANA3_EN_LDOD BIT(24) 97 #define SUN6I_DPHY_ANA3_EN_LDOR BIT(18) 98 99 #define SUN6I_DPHY_ANA4_REG 0x5c 100 #define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24) 101 #define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20) 102 #define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12) 103 #define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10) 104 #define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8) 105 #define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6) 106 #define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4) 107 #define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2) 108 #define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3) 109 110 #define SUN6I_DPHY_DBG5_REG 0xf4 111 112 enum sun6i_dphy_direction { 113 SUN6I_DPHY_DIRECTION_TX, 114 SUN6I_DPHY_DIRECTION_RX, 115 }; 116 117 struct sun6i_dphy_variant { 118 bool rx_supported; 119 }; 120 121 struct sun6i_dphy { 122 struct clk *bus_clk; 123 struct clk *mod_clk; 124 struct regmap *regs; 125 struct reset_control *reset; 126 127 struct phy *phy; 128 struct phy_configure_opts_mipi_dphy config; 129 130 const struct sun6i_dphy_variant *variant; 131 enum sun6i_dphy_direction direction; 132 }; 133 134 static int sun6i_dphy_init(struct phy *phy) 135 { 136 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 137 138 reset_control_deassert(dphy->reset); 139 clk_prepare_enable(dphy->mod_clk); 140 clk_set_rate_exclusive(dphy->mod_clk, 150000000); 141 142 return 0; 143 } 144 145 static int sun6i_dphy_configure(struct phy *phy, union phy_configure_opts *opts) 146 { 147 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 148 int ret; 149 150 ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); 151 if (ret) 152 return ret; 153 154 memcpy(&dphy->config, opts, sizeof(dphy->config)); 155 156 return 0; 157 } 158 159 static int sun6i_dphy_tx_power_on(struct sun6i_dphy *dphy) 160 { 161 u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0); 162 163 regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, 164 SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); 165 166 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG, 167 SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) | 168 SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) | 169 SUN6I_DPHY_TX_TIME0_HS_TRAIL(10)); 170 171 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG, 172 SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) | 173 SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) | 174 SUN6I_DPHY_TX_TIME1_CLK_PRE(3) | 175 SUN6I_DPHY_TX_TIME1_CLK_POST(10)); 176 177 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG, 178 SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30)); 179 180 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0); 181 182 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG, 183 SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) | 184 SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3)); 185 186 regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, 187 SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | 188 SUN6I_DPHY_GCTL_EN); 189 190 regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, 191 SUN6I_DPHY_ANA0_REG_PWS | 192 SUN6I_DPHY_ANA0_REG_DMPC | 193 SUN6I_DPHY_ANA0_REG_SLV(7) | 194 SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) | 195 SUN6I_DPHY_ANA0_REG_DEN(lanes_mask)); 196 197 regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, 198 SUN6I_DPHY_ANA1_REG_CSMPS(1) | 199 SUN6I_DPHY_ANA1_REG_SVTT(7)); 200 201 regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, 202 SUN6I_DPHY_ANA4_REG_CKDV(1) | 203 SUN6I_DPHY_ANA4_REG_TMSC(1) | 204 SUN6I_DPHY_ANA4_REG_TMSD(1) | 205 SUN6I_DPHY_ANA4_REG_TXDNSC(1) | 206 SUN6I_DPHY_ANA4_REG_TXDNSD(1) | 207 SUN6I_DPHY_ANA4_REG_TXPUSC(1) | 208 SUN6I_DPHY_ANA4_REG_TXPUSD(1) | 209 SUN6I_DPHY_ANA4_REG_DMPLVC | 210 SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask)); 211 212 regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, 213 SUN6I_DPHY_ANA2_REG_ENIB); 214 udelay(5); 215 216 regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, 217 SUN6I_DPHY_ANA3_EN_LDOR | 218 SUN6I_DPHY_ANA3_EN_LDOC | 219 SUN6I_DPHY_ANA3_EN_LDOD); 220 udelay(1); 221 222 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, 223 SUN6I_DPHY_ANA3_EN_VTTC | 224 SUN6I_DPHY_ANA3_EN_VTTD_MASK, 225 SUN6I_DPHY_ANA3_EN_VTTC | 226 SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask)); 227 udelay(1); 228 229 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, 230 SUN6I_DPHY_ANA3_EN_DIV, 231 SUN6I_DPHY_ANA3_EN_DIV); 232 udelay(1); 233 234 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, 235 SUN6I_DPHY_ANA2_EN_CK_CPU, 236 SUN6I_DPHY_ANA2_EN_CK_CPU); 237 udelay(1); 238 239 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, 240 SUN6I_DPHY_ANA1_REG_VTTMODE, 241 SUN6I_DPHY_ANA1_REG_VTTMODE); 242 243 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, 244 SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK, 245 SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask)); 246 247 return 0; 248 } 249 250 static int sun6i_dphy_rx_power_on(struct sun6i_dphy *dphy) 251 { 252 /* Physical clock rate is actually half of symbol rate with DDR. */ 253 unsigned long mipi_symbol_rate = dphy->config.hs_clk_rate; 254 unsigned long dphy_clk_rate; 255 unsigned int rx_dly; 256 unsigned int lprst_dly; 257 u32 value; 258 259 dphy_clk_rate = clk_get_rate(dphy->mod_clk); 260 if (!dphy_clk_rate) 261 return -EINVAL; 262 263 /* Hardcoded timing parameters from the Allwinner BSP. */ 264 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME0_REG, 265 SUN6I_DPHY_RX_TIME0_HS_RX_SYNC(255) | 266 SUN6I_DPHY_RX_TIME0_HS_RX_CLK_MISS(255) | 267 SUN6I_DPHY_RX_TIME0_LP_RX(255)); 268 269 /* 270 * Formula from the Allwinner BSP, with hardcoded coefficients 271 * (probably internal divider/multiplier). 272 */ 273 rx_dly = 8 * (unsigned int)(dphy_clk_rate / (mipi_symbol_rate / 8)); 274 275 /* 276 * The Allwinner BSP has an alternative formula for LP_RX_ULPS_WP: 277 * lp_ulps_wp_cnt = lp_ulps_wp_ms * lp_clk / 1000 278 * but does not use it and hardcodes 255 instead. 279 */ 280 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME1_REG, 281 SUN6I_DPHY_RX_TIME1_RX_DLY(rx_dly) | 282 SUN6I_DPHY_RX_TIME1_LP_RX_ULPS_WP(255)); 283 284 /* HS_RX_ANA0 value is hardcoded in the Allwinner BSP. */ 285 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME2_REG, 286 SUN6I_DPHY_RX_TIME2_HS_RX_ANA0(4)); 287 288 /* 289 * Formula from the Allwinner BSP, with hardcoded coefficients 290 * (probably internal divider/multiplier). 291 */ 292 lprst_dly = 4 * (unsigned int)(dphy_clk_rate / (mipi_symbol_rate / 2)); 293 294 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME3_REG, 295 SUN6I_DPHY_RX_TIME3_LPRST_DLY(lprst_dly)); 296 297 /* Analog parameters are hardcoded in the Allwinner BSP. */ 298 regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, 299 SUN6I_DPHY_ANA0_REG_PWS | 300 SUN6I_DPHY_ANA0_REG_SLV(7) | 301 SUN6I_DPHY_ANA0_REG_SFB(2)); 302 303 regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, 304 SUN6I_DPHY_ANA1_REG_SVTT(4)); 305 306 regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, 307 SUN6I_DPHY_ANA4_REG_DMPLVC | 308 SUN6I_DPHY_ANA4_REG_DMPLVD(1)); 309 310 regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, 311 SUN6I_DPHY_ANA2_REG_ENIB); 312 313 regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, 314 SUN6I_DPHY_ANA3_EN_LDOR | 315 SUN6I_DPHY_ANA3_EN_LDOC | 316 SUN6I_DPHY_ANA3_EN_LDOD); 317 318 /* 319 * Delay comes from the Allwinner BSP, likely for internal regulator 320 * ramp-up. 321 */ 322 udelay(3); 323 324 value = SUN6I_DPHY_RX_CTL_EN_DBC | SUN6I_DPHY_RX_CTL_RX_CLK_FORCE; 325 326 /* 327 * Rx data lane force-enable bits are used as regular RX enable by the 328 * Allwinner BSP. 329 */ 330 if (dphy->config.lanes >= 1) 331 value |= SUN6I_DPHY_RX_CTL_RX_D0_FORCE; 332 if (dphy->config.lanes >= 2) 333 value |= SUN6I_DPHY_RX_CTL_RX_D1_FORCE; 334 if (dphy->config.lanes >= 3) 335 value |= SUN6I_DPHY_RX_CTL_RX_D2_FORCE; 336 if (dphy->config.lanes == 4) 337 value |= SUN6I_DPHY_RX_CTL_RX_D3_FORCE; 338 339 regmap_write(dphy->regs, SUN6I_DPHY_RX_CTL_REG, value); 340 341 regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, 342 SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | 343 SUN6I_DPHY_GCTL_EN); 344 345 return 0; 346 } 347 348 static int sun6i_dphy_power_on(struct phy *phy) 349 { 350 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 351 352 switch (dphy->direction) { 353 case SUN6I_DPHY_DIRECTION_TX: 354 return sun6i_dphy_tx_power_on(dphy); 355 case SUN6I_DPHY_DIRECTION_RX: 356 return sun6i_dphy_rx_power_on(dphy); 357 default: 358 return -EINVAL; 359 } 360 } 361 362 static int sun6i_dphy_power_off(struct phy *phy) 363 { 364 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 365 366 regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, 0); 367 368 regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, 0); 369 regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, 0); 370 regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, 0); 371 regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, 0); 372 regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, 0); 373 374 return 0; 375 } 376 377 static int sun6i_dphy_exit(struct phy *phy) 378 { 379 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 380 381 clk_rate_exclusive_put(dphy->mod_clk); 382 clk_disable_unprepare(dphy->mod_clk); 383 reset_control_assert(dphy->reset); 384 385 return 0; 386 } 387 388 389 static const struct phy_ops sun6i_dphy_ops = { 390 .configure = sun6i_dphy_configure, 391 .power_on = sun6i_dphy_power_on, 392 .power_off = sun6i_dphy_power_off, 393 .init = sun6i_dphy_init, 394 .exit = sun6i_dphy_exit, 395 }; 396 397 static const struct regmap_config sun6i_dphy_regmap_config = { 398 .reg_bits = 32, 399 .val_bits = 32, 400 .reg_stride = 4, 401 .max_register = SUN6I_DPHY_DBG5_REG, 402 .name = "mipi-dphy", 403 }; 404 405 static int sun6i_dphy_probe(struct platform_device *pdev) 406 { 407 struct phy_provider *phy_provider; 408 struct sun6i_dphy *dphy; 409 const char *direction; 410 void __iomem *regs; 411 int ret; 412 413 dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); 414 if (!dphy) 415 return -ENOMEM; 416 417 dphy->variant = device_get_match_data(&pdev->dev); 418 if (!dphy->variant) 419 return -EINVAL; 420 421 regs = devm_platform_ioremap_resource(pdev, 0); 422 if (IS_ERR(regs)) { 423 dev_err(&pdev->dev, "Couldn't map the DPHY encoder registers\n"); 424 return PTR_ERR(regs); 425 } 426 427 dphy->regs = devm_regmap_init_mmio_clk(&pdev->dev, "bus", 428 regs, &sun6i_dphy_regmap_config); 429 if (IS_ERR(dphy->regs)) { 430 dev_err(&pdev->dev, "Couldn't create the DPHY encoder regmap\n"); 431 return PTR_ERR(dphy->regs); 432 } 433 434 dphy->reset = devm_reset_control_get_shared(&pdev->dev, NULL); 435 if (IS_ERR(dphy->reset)) { 436 dev_err(&pdev->dev, "Couldn't get our reset line\n"); 437 return PTR_ERR(dphy->reset); 438 } 439 440 dphy->mod_clk = devm_clk_get(&pdev->dev, "mod"); 441 if (IS_ERR(dphy->mod_clk)) { 442 dev_err(&pdev->dev, "Couldn't get the DPHY mod clock\n"); 443 return PTR_ERR(dphy->mod_clk); 444 } 445 446 dphy->phy = devm_phy_create(&pdev->dev, NULL, &sun6i_dphy_ops); 447 if (IS_ERR(dphy->phy)) { 448 dev_err(&pdev->dev, "failed to create PHY\n"); 449 return PTR_ERR(dphy->phy); 450 } 451 452 dphy->direction = SUN6I_DPHY_DIRECTION_TX; 453 454 ret = of_property_read_string(pdev->dev.of_node, "allwinner,direction", 455 &direction); 456 457 if (!ret && !strncmp(direction, "rx", 2)) { 458 if (!dphy->variant->rx_supported) { 459 dev_err(&pdev->dev, "RX not supported on this variant\n"); 460 return -EOPNOTSUPP; 461 } 462 463 dphy->direction = SUN6I_DPHY_DIRECTION_RX; 464 } 465 466 phy_set_drvdata(dphy->phy, dphy); 467 phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); 468 469 return PTR_ERR_OR_ZERO(phy_provider); 470 } 471 472 static const struct sun6i_dphy_variant sun6i_a31_mipi_dphy_variant = { 473 .rx_supported = true, 474 }; 475 476 static const struct of_device_id sun6i_dphy_of_table[] = { 477 { 478 .compatible = "allwinner,sun6i-a31-mipi-dphy", 479 .data = &sun6i_a31_mipi_dphy_variant, 480 }, 481 { } 482 }; 483 MODULE_DEVICE_TABLE(of, sun6i_dphy_of_table); 484 485 static struct platform_driver sun6i_dphy_platform_driver = { 486 .probe = sun6i_dphy_probe, 487 .driver = { 488 .name = "sun6i-mipi-dphy", 489 .of_match_table = sun6i_dphy_of_table, 490 }, 491 }; 492 module_platform_driver(sun6i_dphy_platform_driver); 493 494 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin>"); 495 MODULE_DESCRIPTION("Allwinner A31 MIPI D-PHY Driver"); 496 MODULE_LICENSE("GPL"); 497