aw_lcdclk.c revision 297627
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 297627 2016-04-06 23:11:03Z jmcneill $ 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 297627 2016-04-06 23:11:03Z jmcneill $"); 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; 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 if ((val & CH1_SCLK1_SEL) == CH1_SCLK1_SEL_SCLK2_DIV2) 285 *freq /= 2; 286 } 287 288 return (0); 289} 290 291static void 292calc_tcon_pll(uint64_t fin, uint64_t fout, uint32_t *pm, uint32_t *pn) 293{ 294 int64_t diff, fcur, best; 295 int m, n; 296 297 best = fout; 298 for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) { 299 for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) { 300 fcur = (n * fin) / m; 301 diff = (int64_t)fout - fcur; 302 if (diff > 0 && diff < best) { 303 best = diff; 304 *pm = m; 305 *pn = n; 306 } 307 } 308 } 309} 310 311static int 312aw_lcdclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, 313 int flags, int *stop) 314{ 315 struct aw_lcdclk_softc *sc; 316 uint32_t val, m, m2, n, n2, src_sel; 317 uint64_t fsingle, fdouble; 318 int error; 319 bool dbl; 320 321 sc = clknode_get_softc(clk); 322 323 switch (sc->type) { 324 case AW_LCD_CH0: 325 *stop = 0; 326 break; 327 case AW_LCD_CH1: 328 if (sc->id != CLK_IDX_CH1_SCLK2) 329 return (ENXIO); 330 331 m = n = m2 = n2 = 0; 332 dbl = false; 333 334 /* Find the frequency closes to the target dot clock, using 335 * both 1X and 2X PLL inputs as possible candidates. 336 */ 337 calc_tcon_pll(TCON_PLLREF, *fout, &m, &n); 338 calc_tcon_pll(TCON_PLLREF * 2, *fout, &m2, &n2); 339 340 fsingle = m ? (n * TCON_PLLREF) / m : 0; 341 fdouble = m2 ? (n2 * TCON_PLLREF * 2) / m2 : 0; 342 343 if (fdouble > fsingle) { 344 dbl = true; 345 m = m2; 346 n = n2; 347 } 348 349 src_sel = dbl ? CH0_CLK_SRC_SEL_PLL3_2X : 350 CH0_CLK_SRC_SEL_PLL3_1X; 351 352 /* Switch parent clock if necessary */ 353 if (src_sel != clknode_get_parent_idx(clk)) { 354 error = clknode_set_parent_by_idx(clk, src_sel); 355 if (error != 0) 356 return (error); 357 } 358 359 /* Set desired parent frequency */ 360 fin = n * TCON_PLLREF; 361 362 error = clknode_set_freq(clknode_get_parent(clk), fin, 0, 0); 363 if (error != 0) 364 return (error); 365 366 error = clknode_enable(clknode_get_parent(clk)); 367 if (error != 0) 368 return (error); 369 370 /* Fetch new input frequency */ 371 error = clknode_get_freq(clknode_get_parent(clk), &fin); 372 if (error != 0) 373 return (error); 374 375 /* Set LCD divisor */ 376 DEVICE_LOCK(sc); 377 LCDCLK_READ(sc, &val); 378 val &= ~CH1_CLK_DIV_RATIO_M; 379 val |= ((m - 1) << CH1_CLK_DIV_RATIO_M_SHIFT); 380 LCDCLK_WRITE(sc, val); 381 DEVICE_UNLOCK(sc); 382 383 *fout = fin / m; 384 *stop = 1; 385 386 break; 387 } 388 389 return (0); 390} 391 392static clknode_method_t aw_lcdclk_clknode_methods[] = { 393 /* Device interface */ 394 CLKNODEMETHOD(clknode_init, aw_lcdclk_init), 395 CLKNODEMETHOD(clknode_set_gate, aw_lcdclk_set_gate), 396 CLKNODEMETHOD(clknode_set_mux, aw_lcdclk_set_mux), 397 CLKNODEMETHOD(clknode_recalc_freq, aw_lcdclk_recalc_freq), 398 CLKNODEMETHOD(clknode_set_freq, aw_lcdclk_set_freq), 399 CLKNODEMETHOD_END 400}; 401DEFINE_CLASS_1(aw_lcdclk_clknode, aw_lcdclk_clknode_class, 402 aw_lcdclk_clknode_methods, sizeof(struct aw_lcdclk_softc), clknode_class); 403 404static int 405aw_lcdclk_create(device_t dev, struct clkdom *clkdom, 406 const char **parent_names, int parent_cnt, const char *name, int index) 407{ 408 struct aw_lcdclk_softc *sc, *clk_sc; 409 struct clknode_init_def def; 410 struct clknode *clk; 411 phandle_t node; 412 413 sc = device_get_softc(dev); 414 node = ofw_bus_get_node(dev); 415 416 memset(&def, 0, sizeof(def)); 417 def.id = index; 418 def.name = name; 419 def.parent_names = parent_names; 420 def.parent_cnt = parent_cnt; 421 422 clk = clknode_create(clkdom, &aw_lcdclk_clknode_class, &def); 423 if (clk == NULL) { 424 device_printf(dev, "cannot create clknode\n"); 425 return (ENXIO); 426 } 427 428 clk_sc = clknode_get_softc(clk); 429 clk_sc->type = sc->type; 430 clk_sc->reg = sc->reg; 431 clk_sc->clkdev = sc->clkdev; 432 clk_sc->id = index; 433 434 clknode_register(clkdom, clk); 435 436 return (0); 437} 438 439static int 440aw_lcdclk_probe(device_t dev) 441{ 442 enum aw_lcdclk_type type; 443 444 if (!ofw_bus_status_okay(dev)) 445 return (ENXIO); 446 447 type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; 448 switch (type) { 449 case AW_LCD_CH0: 450 device_set_desc(dev, "Allwinner LCD CH0 Clock"); 451 break; 452 case AW_LCD_CH1: 453 device_set_desc(dev, "Allwinner LCD CH1 Clock"); 454 break; 455 default: 456 return (ENXIO); 457 } 458 459 return (BUS_PROBE_DEFAULT); 460} 461 462static int 463aw_lcdclk_attach(device_t dev) 464{ 465 struct aw_lcdclk_softc *sc; 466 struct clkdom *clkdom; 467 clk_t clk_parent; 468 bus_size_t psize; 469 phandle_t node; 470 uint32_t *indices; 471 const char **parent_names; 472 const char **names; 473 int error, ncells, nout, i; 474 475 sc = device_get_softc(dev); 476 sc->clkdev = device_get_parent(dev); 477 sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; 478 479 node = ofw_bus_get_node(dev); 480 481 if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) { 482 device_printf(dev, "cannot parse 'reg' property\n"); 483 return (ENXIO); 484 } 485 486 error = ofw_bus_parse_xref_list_get_length(node, "clocks", 487 "#clock-cells", &ncells); 488 if (error != 0) { 489 device_printf(dev, "cannot get clock count\n"); 490 return (error); 491 } 492 493 parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); 494 for (i = 0; i < ncells; i++) { 495 error = clk_get_by_ofw_index(dev, i, &clk_parent); 496 if (error != 0) { 497 device_printf(dev, "cannot get clock %d\n", i); 498 goto fail; 499 } 500 parent_names[i] = clk_get_name(clk_parent); 501 clk_release(clk_parent); 502 } 503 504 nout = clk_parse_ofw_out_names(dev, node, &names, &indices); 505 if (nout == 0) { 506 device_printf(dev, "no clock outputs found\n"); 507 return (error); 508 } 509 510 clkdom = clkdom_create(dev); 511 512 for (i = 0; i < nout; i++) { 513 error = aw_lcdclk_create(dev, clkdom, parent_names, ncells, 514 names[i], nout == 1 ? 1 : i); 515 if (error) 516 goto fail; 517 } 518 519 if (clkdom_finit(clkdom) != 0) { 520 device_printf(dev, "cannot finalize clkdom initialization\n"); 521 error = ENXIO; 522 goto fail; 523 } 524 525 if (bootverbose) 526 clkdom_dump(clkdom); 527 528 if (sc->type == AW_LCD_CH0) 529 hwreset_register_ofw_provider(dev); 530 531 free(parent_names, M_OFWPROP); 532 return (0); 533 534fail: 535 free(parent_names, M_OFWPROP); 536 return (error); 537} 538 539static device_method_t aw_lcdclk_methods[] = { 540 /* Device interface */ 541 DEVMETHOD(device_probe, aw_lcdclk_probe), 542 DEVMETHOD(device_attach, aw_lcdclk_attach), 543 544 /* Reset interface */ 545 DEVMETHOD(hwreset_assert, aw_lcdclk_hwreset_assert), 546 DEVMETHOD(hwreset_is_asserted, aw_lcdclk_hwreset_is_asserted), 547 548 DEVMETHOD_END 549}; 550 551static driver_t aw_lcdclk_driver = { 552 "aw_lcdclk", 553 aw_lcdclk_methods, 554 sizeof(struct aw_lcdclk_softc) 555}; 556 557static devclass_t aw_lcdclk_devclass; 558 559EARLY_DRIVER_MODULE(aw_lcdclk, simplebus, aw_lcdclk_driver, 560 aw_lcdclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 561