18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * OF helpers for parsing display timings 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * based on of_videomode.c by Sascha Hauer <s.hauer@pengutronix.de> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci#include <linux/export.h> 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <video/display_timing.h> 138c2ecf20Sopenharmony_ci#include <video/of_display_timing.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci/** 168c2ecf20Sopenharmony_ci * parse_timing_property - parse timing_entry from device_node 178c2ecf20Sopenharmony_ci * @np: device_node with the property 188c2ecf20Sopenharmony_ci * @name: name of the property 198c2ecf20Sopenharmony_ci * @result: will be set to the return value 208c2ecf20Sopenharmony_ci * 218c2ecf20Sopenharmony_ci * DESCRIPTION: 228c2ecf20Sopenharmony_ci * Every display_timing can be specified with either just the typical value or 238c2ecf20Sopenharmony_ci * a range consisting of min/typ/max. This function helps handling this 248c2ecf20Sopenharmony_ci **/ 258c2ecf20Sopenharmony_cistatic int parse_timing_property(const struct device_node *np, const char *name, 268c2ecf20Sopenharmony_ci struct timing_entry *result) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci struct property *prop; 298c2ecf20Sopenharmony_ci int length, cells, ret; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci prop = of_find_property(np, name, &length); 328c2ecf20Sopenharmony_ci if (!prop) { 338c2ecf20Sopenharmony_ci pr_err("%pOF: could not find property %s\n", np, name); 348c2ecf20Sopenharmony_ci return -EINVAL; 358c2ecf20Sopenharmony_ci } 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci cells = length / sizeof(u32); 388c2ecf20Sopenharmony_ci if (cells == 1) { 398c2ecf20Sopenharmony_ci ret = of_property_read_u32(np, name, &result->typ); 408c2ecf20Sopenharmony_ci result->min = result->typ; 418c2ecf20Sopenharmony_ci result->max = result->typ; 428c2ecf20Sopenharmony_ci } else if (cells == 3) { 438c2ecf20Sopenharmony_ci ret = of_property_read_u32_array(np, name, &result->min, cells); 448c2ecf20Sopenharmony_ci } else { 458c2ecf20Sopenharmony_ci pr_err("%pOF: illegal timing specification in %s\n", np, name); 468c2ecf20Sopenharmony_ci return -EINVAL; 478c2ecf20Sopenharmony_ci } 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return ret; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci/** 538c2ecf20Sopenharmony_ci * of_parse_display_timing - parse display_timing entry from device_node 548c2ecf20Sopenharmony_ci * @np: device_node with the properties 558c2ecf20Sopenharmony_ci **/ 568c2ecf20Sopenharmony_cistatic int of_parse_display_timing(const struct device_node *np, 578c2ecf20Sopenharmony_ci struct display_timing *dt) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci u32 val = 0; 608c2ecf20Sopenharmony_ci int ret = 0; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci memset(dt, 0, sizeof(*dt)); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "hback-porch", &dt->hback_porch); 658c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "hfront-porch", &dt->hfront_porch); 668c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "hactive", &dt->hactive); 678c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "hsync-len", &dt->hsync_len); 688c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "vback-porch", &dt->vback_porch); 698c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "vfront-porch", &dt->vfront_porch); 708c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "vactive", &dt->vactive); 718c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "vsync-len", &dt->vsync_len); 728c2ecf20Sopenharmony_ci ret |= parse_timing_property(np, "clock-frequency", &dt->pixelclock); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci dt->flags = 0; 758c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "vsync-active", &val)) 768c2ecf20Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_VSYNC_HIGH : 778c2ecf20Sopenharmony_ci DISPLAY_FLAGS_VSYNC_LOW; 788c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "hsync-active", &val)) 798c2ecf20Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_HSYNC_HIGH : 808c2ecf20Sopenharmony_ci DISPLAY_FLAGS_HSYNC_LOW; 818c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "de-active", &val)) 828c2ecf20Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_DE_HIGH : 838c2ecf20Sopenharmony_ci DISPLAY_FLAGS_DE_LOW; 848c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "pixelclk-active", &val)) 858c2ecf20Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_PIXDATA_POSEDGE : 868c2ecf20Sopenharmony_ci DISPLAY_FLAGS_PIXDATA_NEGEDGE; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "syncclk-active", &val)) 898c2ecf20Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_SYNC_POSEDGE : 908c2ecf20Sopenharmony_ci DISPLAY_FLAGS_SYNC_NEGEDGE; 918c2ecf20Sopenharmony_ci else if (dt->flags & (DISPLAY_FLAGS_PIXDATA_POSEDGE | 928c2ecf20Sopenharmony_ci DISPLAY_FLAGS_PIXDATA_NEGEDGE)) 938c2ecf20Sopenharmony_ci dt->flags |= dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE ? 948c2ecf20Sopenharmony_ci DISPLAY_FLAGS_SYNC_POSEDGE : 958c2ecf20Sopenharmony_ci DISPLAY_FLAGS_SYNC_NEGEDGE; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "interlaced")) 988c2ecf20Sopenharmony_ci dt->flags |= DISPLAY_FLAGS_INTERLACED; 998c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "doublescan")) 1008c2ecf20Sopenharmony_ci dt->flags |= DISPLAY_FLAGS_DOUBLESCAN; 1018c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "doubleclk")) 1028c2ecf20Sopenharmony_ci dt->flags |= DISPLAY_FLAGS_DOUBLECLK; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (ret) { 1058c2ecf20Sopenharmony_ci pr_err("%pOF: error reading timing properties\n", np); 1068c2ecf20Sopenharmony_ci return -EINVAL; 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci return 0; 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci/** 1138c2ecf20Sopenharmony_ci * of_get_display_timing - parse a display_timing entry 1148c2ecf20Sopenharmony_ci * @np: device_node with the timing subnode 1158c2ecf20Sopenharmony_ci * @name: name of the timing node 1168c2ecf20Sopenharmony_ci * @dt: display_timing struct to fill 1178c2ecf20Sopenharmony_ci **/ 1188c2ecf20Sopenharmony_ciint of_get_display_timing(const struct device_node *np, const char *name, 1198c2ecf20Sopenharmony_ci struct display_timing *dt) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct device_node *timing_np; 1228c2ecf20Sopenharmony_ci int ret; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (!np) 1258c2ecf20Sopenharmony_ci return -EINVAL; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci timing_np = of_get_child_by_name(np, name); 1288c2ecf20Sopenharmony_ci if (!timing_np) 1298c2ecf20Sopenharmony_ci return -ENOENT; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci ret = of_parse_display_timing(timing_np, dt); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci of_node_put(timing_np); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return ret; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(of_get_display_timing); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci/** 1408c2ecf20Sopenharmony_ci * of_get_display_timings - parse all display_timing entries from a device_node 1418c2ecf20Sopenharmony_ci * @np: device_node with the subnodes 1428c2ecf20Sopenharmony_ci **/ 1438c2ecf20Sopenharmony_cistruct display_timings *of_get_display_timings(const struct device_node *np) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci struct device_node *timings_np; 1468c2ecf20Sopenharmony_ci struct device_node *entry; 1478c2ecf20Sopenharmony_ci struct device_node *native_mode; 1488c2ecf20Sopenharmony_ci struct display_timings *disp; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci if (!np) 1518c2ecf20Sopenharmony_ci return NULL; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci timings_np = of_get_child_by_name(np, "display-timings"); 1548c2ecf20Sopenharmony_ci if (!timings_np) { 1558c2ecf20Sopenharmony_ci pr_err("%pOF: could not find display-timings node\n", np); 1568c2ecf20Sopenharmony_ci return NULL; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci disp = kzalloc(sizeof(*disp), GFP_KERNEL); 1608c2ecf20Sopenharmony_ci if (!disp) { 1618c2ecf20Sopenharmony_ci pr_err("%pOF: could not allocate struct disp'\n", np); 1628c2ecf20Sopenharmony_ci goto dispfail; 1638c2ecf20Sopenharmony_ci } 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci entry = of_parse_phandle(timings_np, "native-mode", 0); 1668c2ecf20Sopenharmony_ci /* assume first child as native mode if none provided */ 1678c2ecf20Sopenharmony_ci if (!entry) 1688c2ecf20Sopenharmony_ci entry = of_get_next_child(timings_np, NULL); 1698c2ecf20Sopenharmony_ci /* if there is no child, it is useless to go on */ 1708c2ecf20Sopenharmony_ci if (!entry) { 1718c2ecf20Sopenharmony_ci pr_err("%pOF: no timing specifications given\n", np); 1728c2ecf20Sopenharmony_ci goto entryfail; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci pr_debug("%pOF: using %pOFn as default timing\n", np, entry); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci native_mode = entry; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci disp->num_timings = of_get_child_count(timings_np); 1808c2ecf20Sopenharmony_ci if (disp->num_timings == 0) { 1818c2ecf20Sopenharmony_ci /* should never happen, as entry was already found above */ 1828c2ecf20Sopenharmony_ci pr_err("%pOF: no timings specified\n", np); 1838c2ecf20Sopenharmony_ci goto entryfail; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci disp->timings = kcalloc(disp->num_timings, 1878c2ecf20Sopenharmony_ci sizeof(struct display_timing *), 1888c2ecf20Sopenharmony_ci GFP_KERNEL); 1898c2ecf20Sopenharmony_ci if (!disp->timings) { 1908c2ecf20Sopenharmony_ci pr_err("%pOF: could not allocate timings array\n", np); 1918c2ecf20Sopenharmony_ci goto entryfail; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci disp->num_timings = 0; 1958c2ecf20Sopenharmony_ci disp->native_mode = 0; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci for_each_child_of_node(timings_np, entry) { 1988c2ecf20Sopenharmony_ci struct display_timing *dt; 1998c2ecf20Sopenharmony_ci int r; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci dt = kzalloc(sizeof(*dt), GFP_KERNEL); 2028c2ecf20Sopenharmony_ci if (!dt) { 2038c2ecf20Sopenharmony_ci pr_err("%pOF: could not allocate display_timing struct\n", 2048c2ecf20Sopenharmony_ci np); 2058c2ecf20Sopenharmony_ci goto timingfail; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci r = of_parse_display_timing(entry, dt); 2098c2ecf20Sopenharmony_ci if (r) { 2108c2ecf20Sopenharmony_ci /* 2118c2ecf20Sopenharmony_ci * to not encourage wrong devicetrees, fail in case of 2128c2ecf20Sopenharmony_ci * an error 2138c2ecf20Sopenharmony_ci */ 2148c2ecf20Sopenharmony_ci pr_err("%pOF: error in timing %d\n", 2158c2ecf20Sopenharmony_ci np, disp->num_timings + 1); 2168c2ecf20Sopenharmony_ci kfree(dt); 2178c2ecf20Sopenharmony_ci goto timingfail; 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci if (native_mode == entry) 2218c2ecf20Sopenharmony_ci disp->native_mode = disp->num_timings; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci disp->timings[disp->num_timings] = dt; 2248c2ecf20Sopenharmony_ci disp->num_timings++; 2258c2ecf20Sopenharmony_ci } 2268c2ecf20Sopenharmony_ci of_node_put(timings_np); 2278c2ecf20Sopenharmony_ci /* 2288c2ecf20Sopenharmony_ci * native_mode points to the device_node returned by of_parse_phandle 2298c2ecf20Sopenharmony_ci * therefore call of_node_put on it 2308c2ecf20Sopenharmony_ci */ 2318c2ecf20Sopenharmony_ci of_node_put(native_mode); 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci pr_debug("%pOF: got %d timings. Using timing #%d as default\n", 2348c2ecf20Sopenharmony_ci np, disp->num_timings, 2358c2ecf20Sopenharmony_ci disp->native_mode + 1); 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci return disp; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_citimingfail: 2408c2ecf20Sopenharmony_ci of_node_put(native_mode); 2418c2ecf20Sopenharmony_ci display_timings_release(disp); 2428c2ecf20Sopenharmony_ci disp = NULL; 2438c2ecf20Sopenharmony_cientryfail: 2448c2ecf20Sopenharmony_ci kfree(disp); 2458c2ecf20Sopenharmony_cidispfail: 2468c2ecf20Sopenharmony_ci of_node_put(timings_np); 2478c2ecf20Sopenharmony_ci return NULL; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(of_get_display_timings); 250