aw_lcdclk.c revision 299703
1/*- 2 * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: head/sys/arm/allwinner/clk/aw_lcdclk.c 299703 2016-05-13 22:28:02Z gonzo $ 27 */ 28 29/* 30 * Allwinner LCD clocks 31 */ 32 33#include <sys/cdefs.h> 34__FBSDID("$FreeBSD: head/sys/arm/allwinner/clk/aw_lcdclk.c 299703 2016-05-13 22:28:02Z gonzo $"); 35 36#include <sys/param.h> 37#include <sys/systm.h> 38#include <sys/bus.h> 39#include <sys/rman.h> 40#include <sys/kernel.h> 41#include <sys/module.h> 42#include <machine/bus.h> 43 44#include <dev/ofw/ofw_bus.h> 45#include <dev/ofw/ofw_bus_subr.h> 46#include <dev/ofw/ofw_subr.h> 47 48#include <dev/extres/clk/clk.h> 49#include <dev/extres/hwreset/hwreset.h> 50 51#include "clkdev_if.h" 52#include "hwreset_if.h" 53 54/* CH0 */ 55#define CH0_SCLK_GATING (1 << 31) 56#define CH0_LCD_RST (1 << 30) 57#define CH0_CLK_SRC_SEL (0x3 << 24) 58#define CH0_CLK_SRC_SEL_SHIFT 24 59#define CH0_CLK_SRC_SEL_PLL3_1X 0 60#define CH0_CLK_SRC_SEL_PLL7_1X 1 61#define CH0_CLK_SRC_SEL_PLL3_2X 2 62#define CH0_CLK_SRC_SEL_PLL6 3 63 64/* CH1 */ 65#define CH1_SCLK2_GATING (1 << 31) 66#define CH1_SCLK2_SEL (0x3 << 24) 67#define CH1_SCLK2_SEL_SHIFT 24 68#define CH1_SCLK2_SEL_PLL3_1X 0 69#define CH1_SCLK2_SEL_PLL7_1X 1 70#define CH1_SCLK2_SEL_PLL3_2X 2 71#define CH1_SCLK2_SEL_PLL7_2X 3 72#define CH1_SCLK1_GATING (1 << 15) 73#define CH1_SCLK1_SEL (0x1 << 11) 74#define CH1_SCLK1_SEL_SHIFT 11 75#define CH1_SCLK1_SEL_SCLK2 0 76#define CH1_SCLK1_SEL_SCLK2_DIV2 1 77#define CH1_CLK_DIV_RATIO_M (0x1f << 0) 78#define CH1_CLK_DIV_RATIO_M_SHIFT 0 79 80#define TCON_PLLREF 3000000ULL 81#define TCON_PLL_M_MIN 1 82#define TCON_PLL_M_MAX 15 83#define TCON_PLL_N_MIN 9 84#define TCON_PLL_N_MAX 127 85 86#define CLK_IDX_CH1_SCLK1 0 87#define CLK_IDX_CH1_SCLK2 1 88 89#define CLK_IDX_ 90 91enum aw_lcdclk_type { 92 AW_LCD_CH0 = 1, 93 AW_LCD_CH1, 94}; 95 96static struct ofw_compat_data compat_data[] = { 97 { "allwinner,sun4i-a10-lcd-ch0-clk", AW_LCD_CH0 }, 98 { "allwinner,sun4i-a10-lcd-ch1-clk", AW_LCD_CH1 }, 99 { NULL, 0 } 100}; 101 102struct aw_lcdclk_softc { 103 enum aw_lcdclk_type type; 104 device_t clkdev; 105 bus_addr_t reg; 106 int id; 107}; 108 109#define LCDCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) 110#define LCDCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) 111#define LCDCLK_MODIFY(sc, clr, set) \ 112 CLKDEV_MODIFY_4((sc)->clkdev, (sc)->reg, (clr), (set)) 113#define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) 114#define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) 115 116static int 117aw_lcdclk_hwreset_assert(device_t dev, intptr_t id, bool value) 118{ 119 struct aw_lcdclk_softc *sc; 120 int error; 121 122 sc = device_get_softc(dev); 123 124 if (sc->type != AW_LCD_CH0) 125 return (ENXIO); 126 127 DEVICE_LOCK(sc); 128 error = LCDCLK_MODIFY(sc, CH0_LCD_RST, value ? 0 : CH0_LCD_RST); 129 DEVICE_UNLOCK(sc); 130 131 return (error); 132} 133 134static int 135aw_lcdclk_hwreset_is_asserted(device_t dev, intptr_t id, bool *value) 136{ 137 struct aw_lcdclk_softc *sc; 138 uint32_t val; 139 int error; 140 141 sc = device_get_softc(dev); 142 143 if (sc->type != AW_LCD_CH0) 144 return (ENXIO); 145 146 DEVICE_LOCK(sc); 147 error = LCDCLK_READ(sc, &val); 148 DEVICE_UNLOCK(sc); 149 150 if (error) 151 return (error); 152 153 *value = (val & CH0_LCD_RST) != 0 ? false : true; 154 155 return (0); 156} 157 158static int 159aw_lcdclk_init(struct clknode *clk, device_t dev) 160{ 161 struct aw_lcdclk_softc *sc; 162 uint32_t val, index; 163 164 sc = clknode_get_softc(clk); 165 166 DEVICE_LOCK(sc); 167 LCDCLK_READ(sc, &val); 168 DEVICE_UNLOCK(sc); 169 170 switch (sc->type) { 171 case AW_LCD_CH0: 172 index = (val & CH0_CLK_SRC_SEL) >> CH0_CLK_SRC_SEL_SHIFT; 173 break; 174 case AW_LCD_CH1: 175 switch (sc->id) { 176 case CLK_IDX_CH1_SCLK1: 177 index = 0; 178 break; 179 case CLK_IDX_CH1_SCLK2: 180 index = (val & CH1_SCLK2_SEL_SHIFT) >> 181 CH1_SCLK2_SEL_SHIFT; 182 break; 183 default: 184 return (ENXIO); 185 } 186 break; 187 default: 188 return (ENXIO); 189 } 190 191 clknode_init_parent_idx(clk, index); 192 return (0); 193} 194 195static int 196aw_lcdclk_set_mux(struct clknode *clk, int index) 197{ 198 struct aw_lcdclk_softc *sc; 199 uint32_t val; 200 201 sc = clknode_get_softc(clk); 202 203 switch (sc->type) { 204 case AW_LCD_CH0: 205 DEVICE_LOCK(sc); 206 LCDCLK_READ(sc, &val); 207 val &= ~CH0_CLK_SRC_SEL; 208 val |= (index << CH0_CLK_SRC_SEL_SHIFT); 209 LCDCLK_WRITE(sc, val); 210 DEVICE_UNLOCK(sc); 211 break; 212 case AW_LCD_CH1: 213 switch (sc->id) { 214 case CLK_IDX_CH1_SCLK2: 215 DEVICE_LOCK(sc); 216 LCDCLK_READ(sc, &val); 217 val &= ~CH1_SCLK2_SEL; 218 val |= (index << CH1_SCLK2_SEL_SHIFT); 219 LCDCLK_WRITE(sc, val); 220 DEVICE_UNLOCK(sc); 221 break; 222 default: 223 return (ENXIO); 224 } 225 break; 226 default: 227 return (ENXIO); 228 } 229 230 return (0); 231} 232 233static int 234aw_lcdclk_set_gate(struct clknode *clk, bool enable) 235{ 236 struct aw_lcdclk_softc *sc; 237 uint32_t val, mask; 238 239 sc = clknode_get_softc(clk); 240 241 switch (sc->type) { 242 case AW_LCD_CH0: 243 mask = CH0_SCLK_GATING; 244 break; 245 case AW_LCD_CH1: 246 mask = (sc->id == CLK_IDX_CH1_SCLK1) ? CH1_SCLK1_GATING : 247 CH1_SCLK2_GATING; 248 break; 249 default: 250 return (ENXIO); 251 } 252 253 DEVICE_LOCK(sc); 254 LCDCLK_READ(sc, &val); 255 if (enable) 256 val |= mask; 257 else 258 val &= ~mask; 259 LCDCLK_WRITE(sc, val); 260 DEVICE_UNLOCK(sc); 261 262 return (0); 263} 264 265static int 266aw_lcdclk_recalc_freq(struct clknode *clk, uint64_t *freq) 267{ 268 struct aw_lcdclk_softc *sc; 269 uint32_t val, m, src_sel; 270 271 sc = clknode_get_softc(clk); 272 273 if (sc->type != AW_LCD_CH1) 274 return (0); 275 276 DEVICE_LOCK(sc); 277 LCDCLK_READ(sc, &val); 278 DEVICE_UNLOCK(sc); 279 280 m = ((val & CH1_CLK_DIV_RATIO_M) >> CH1_CLK_DIV_RATIO_M_SHIFT) + 1; 281 *freq = *freq / m; 282 283 if (sc->id == CLK_IDX_CH1_SCLK1) { 284 src_sel = (val & CH1_SCLK1_SEL) >> CH1_SCLK1_SEL_SHIFT; 285 if (src_sel == CH1_SCLK1_SEL_SCLK2_DIV2) 286 *freq /= 2; 287 } 288 289 return (0); 290} 291 292static void 293calc_tcon_pll(uint64_t fin, uint64_t fout, uint32_t *pm, uint32_t *pn) 294{ 295 int64_t diff, fcur, best; 296 int m, n; 297 298 best = fout; 299 for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) { 300 for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) { 301 fcur = (n * fin) / m; 302 diff = (int64_t)fout - fcur; 303 if (diff > 0 && diff < best) { 304 best = diff; 305 *pm = m; 306 *pn = n; 307 } 308 } 309 } 310} 311 312static int 313aw_lcdclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, 314 int flags, int *stop) 315{ 316 struct aw_lcdclk_softc *sc; 317 uint32_t val, m, m2, n, n2, src_sel; 318 uint64_t fsingle, fdouble; 319 int error; 320 bool dbl; 321 322 sc = clknode_get_softc(clk); 323 324 switch (sc->type) { 325 case AW_LCD_CH0: 326 *stop = 0; 327 break; 328 case AW_LCD_CH1: 329 if (sc->id != CLK_IDX_CH1_SCLK2) 330 return (ENXIO); 331 332 m = n = m2 = n2 = 0; 333 dbl = false; 334 335 /* Find the frequency closes to the target dot clock, using 336 * both 1X and 2X PLL inputs as possible candidates. 337 */ 338 calc_tcon_pll(TCON_PLLREF, *fout, &m, &n); 339 calc_tcon_pll(TCON_PLLREF * 2, *fout, &m2, &n2); 340 341 fsingle = m ? (n * TCON_PLLREF) / m : 0; 342 fdouble = m2 ? (n2 * TCON_PLLREF * 2) / m2 : 0; 343 344 if (fdouble > fsingle) { 345 dbl = true; 346 m = m2; 347 n = n2; 348 } 349 350 src_sel = dbl ? CH0_CLK_SRC_SEL_PLL3_2X : 351 CH0_CLK_SRC_SEL_PLL3_1X; 352 353 /* Switch parent clock if necessary */ 354 if (src_sel != clknode_get_parent_idx(clk)) { 355 error = clknode_set_parent_by_idx(clk, src_sel); 356 if (error != 0) 357 return (error); 358 } 359 360 /* Set desired parent frequency */ 361 fin = n * TCON_PLLREF; 362 363 error = clknode_set_freq(clknode_get_parent(clk), fin, 0, 0); 364 if (error != 0) 365 return (error); 366 367 error = clknode_enable(clknode_get_parent(clk)); 368 if (error != 0) 369 return (error); 370 371 /* Fetch new input frequency */ 372 error = clknode_get_freq(clknode_get_parent(clk), &fin); 373 if (error != 0) 374 return (error); 375 376 /* Set LCD divisor */ 377 DEVICE_LOCK(sc); 378 LCDCLK_READ(sc, &val); 379 val &= ~CH1_CLK_DIV_RATIO_M; 380 val |= ((m - 1) << CH1_CLK_DIV_RATIO_M_SHIFT); 381 LCDCLK_WRITE(sc, val); 382 DEVICE_UNLOCK(sc); 383 384 *fout = fin / m; 385 *stop = 1; 386 387 break; 388 } 389 390 return (0); 391} 392 393static clknode_method_t aw_lcdclk_clknode_methods[] = { 394 /* Device interface */ 395 CLKNODEMETHOD(clknode_init, aw_lcdclk_init), 396 CLKNODEMETHOD(clknode_set_gate, aw_lcdclk_set_gate), 397 CLKNODEMETHOD(clknode_set_mux, aw_lcdclk_set_mux), 398 CLKNODEMETHOD(clknode_recalc_freq, aw_lcdclk_recalc_freq), 399 CLKNODEMETHOD(clknode_set_freq, aw_lcdclk_set_freq), 400 CLKNODEMETHOD_END 401}; 402DEFINE_CLASS_1(aw_lcdclk_clknode, aw_lcdclk_clknode_class, 403 aw_lcdclk_clknode_methods, sizeof(struct aw_lcdclk_softc), clknode_class); 404 405static int 406aw_lcdclk_create(device_t dev, struct clkdom *clkdom, 407 const char **parent_names, int parent_cnt, const char *name, int index) 408{ 409 struct aw_lcdclk_softc *sc, *clk_sc; 410 struct clknode_init_def def; 411 struct clknode *clk; 412 phandle_t node; 413 414 sc = device_get_softc(dev); 415 node = ofw_bus_get_node(dev); 416 417 memset(&def, 0, sizeof(def)); 418 def.id = index; 419 def.name = name; 420 def.parent_names = parent_names; 421 def.parent_cnt = parent_cnt; 422 423 clk = clknode_create(clkdom, &aw_lcdclk_clknode_class, &def); 424 if (clk == NULL) { 425 device_printf(dev, "cannot create clknode\n"); 426 return (ENXIO); 427 } 428 429 clk_sc = clknode_get_softc(clk); 430 clk_sc->type = sc->type; 431 clk_sc->reg = sc->reg; 432 clk_sc->clkdev = sc->clkdev; 433 clk_sc->id = index; 434 435 clknode_register(clkdom, clk); 436 437 return (0); 438} 439 440static int 441aw_lcdclk_probe(device_t dev) 442{ 443 enum aw_lcdclk_type type; 444 445 if (!ofw_bus_status_okay(dev)) 446 return (ENXIO); 447 448 type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; 449 switch (type) { 450 case AW_LCD_CH0: 451 device_set_desc(dev, "Allwinner LCD CH0 Clock"); 452 break; 453 case AW_LCD_CH1: 454 device_set_desc(dev, "Allwinner LCD CH1 Clock"); 455 break; 456 default: 457 return (ENXIO); 458 } 459 460 return (BUS_PROBE_DEFAULT); 461} 462 463static int 464aw_lcdclk_attach(device_t dev) 465{ 466 struct aw_lcdclk_softc *sc; 467 struct clkdom *clkdom; 468 clk_t clk_parent; 469 bus_size_t psize; 470 phandle_t node; 471 uint32_t *indices; 472 const char **parent_names; 473 const char **names; 474 int error, ncells, nout, i; 475 476 sc = device_get_softc(dev); 477 sc->clkdev = device_get_parent(dev); 478 sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; 479 480 node = ofw_bus_get_node(dev); 481 482 if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) { 483 device_printf(dev, "cannot parse 'reg' property\n"); 484 return (ENXIO); 485 } 486 487 error = ofw_bus_parse_xref_list_get_length(node, "clocks", 488 "#clock-cells", &ncells); 489 if (error != 0) { 490 device_printf(dev, "cannot get clock count\n"); 491 return (error); 492 } 493 494 parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); 495 for (i = 0; i < ncells; i++) { 496 error = clk_get_by_ofw_index(dev, i, &clk_parent); 497 if (error != 0) { 498 device_printf(dev, "cannot get clock %d\n", i); 499 goto fail; 500 } 501 parent_names[i] = clk_get_name(clk_parent); 502 clk_release(clk_parent); 503 } 504 505 nout = clk_parse_ofw_out_names(dev, node, &names, &indices); 506 if (nout == 0) { 507 device_printf(dev, "no clock outputs found\n"); 508 return (error); 509 } 510 511 clkdom = clkdom_create(dev); 512 513 for (i = 0; i < nout; i++) { 514 error = aw_lcdclk_create(dev, clkdom, parent_names, ncells, 515 names[i], nout == 1 ? 1 : i); 516 if (error) 517 goto fail; 518 } 519 520 if (clkdom_finit(clkdom) != 0) { 521 device_printf(dev, "cannot finalize clkdom initialization\n"); 522 error = ENXIO; 523 goto fail; 524 } 525 526 if (bootverbose) 527 clkdom_dump(clkdom); 528 529 if (sc->type == AW_LCD_CH0) 530 hwreset_register_ofw_provider(dev); 531 532 OF_prop_free(parent_names); 533 return (0); 534 535fail: 536 OF_prop_free(parent_names); 537 return (error); 538} 539 540static device_method_t aw_lcdclk_methods[] = { 541 /* Device interface */ 542 DEVMETHOD(device_probe, aw_lcdclk_probe), 543 DEVMETHOD(device_attach, aw_lcdclk_attach), 544 545 /* Reset interface */ 546 DEVMETHOD(hwreset_assert, aw_lcdclk_hwreset_assert), 547 DEVMETHOD(hwreset_is_asserted, aw_lcdclk_hwreset_is_asserted), 548 549 DEVMETHOD_END 550}; 551 552static driver_t aw_lcdclk_driver = { 553 "aw_lcdclk", 554 aw_lcdclk_methods, 555 sizeof(struct aw_lcdclk_softc) 556}; 557 558static devclass_t aw_lcdclk_devclass; 559 560EARLY_DRIVER_MODULE(aw_lcdclk, simplebus, aw_lcdclk_driver, 561 aw_lcdclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 562