162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * OF helpers for parsing display timings 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * based on of_videomode.c by Sascha Hauer <s.hauer@pengutronix.de> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#include <linux/export.h> 1062306a36Sopenharmony_ci#include <linux/of.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <video/display_timing.h> 1362306a36Sopenharmony_ci#include <video/of_display_timing.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci/** 1662306a36Sopenharmony_ci * parse_timing_property - parse timing_entry from device_node 1762306a36Sopenharmony_ci * @np: device_node with the property 1862306a36Sopenharmony_ci * @name: name of the property 1962306a36Sopenharmony_ci * @result: will be set to the return value 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * DESCRIPTION: 2262306a36Sopenharmony_ci * Every display_timing can be specified with either just the typical value or 2362306a36Sopenharmony_ci * a range consisting of min/typ/max. This function helps handling this 2462306a36Sopenharmony_ci **/ 2562306a36Sopenharmony_cistatic int parse_timing_property(const struct device_node *np, const char *name, 2662306a36Sopenharmony_ci struct timing_entry *result) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci struct property *prop; 2962306a36Sopenharmony_ci int length, cells, ret; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci prop = of_find_property(np, name, &length); 3262306a36Sopenharmony_ci if (!prop) { 3362306a36Sopenharmony_ci pr_err("%pOF: could not find property %s\n", np, name); 3462306a36Sopenharmony_ci return -EINVAL; 3562306a36Sopenharmony_ci } 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci cells = length / sizeof(u32); 3862306a36Sopenharmony_ci if (cells == 1) { 3962306a36Sopenharmony_ci ret = of_property_read_u32(np, name, &result->typ); 4062306a36Sopenharmony_ci result->min = result->typ; 4162306a36Sopenharmony_ci result->max = result->typ; 4262306a36Sopenharmony_ci } else if (cells == 3) { 4362306a36Sopenharmony_ci ret = of_property_read_u32_array(np, name, &result->min, cells); 4462306a36Sopenharmony_ci } else { 4562306a36Sopenharmony_ci pr_err("%pOF: illegal timing specification in %s\n", np, name); 4662306a36Sopenharmony_ci return -EINVAL; 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci return ret; 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci/** 5362306a36Sopenharmony_ci * of_parse_display_timing - parse display_timing entry from device_node 5462306a36Sopenharmony_ci * @np: device_node with the properties 5562306a36Sopenharmony_ci * @dt: display_timing that contains the result. I may be partially written in case of errors 5662306a36Sopenharmony_ci **/ 5762306a36Sopenharmony_cistatic int of_parse_display_timing(const struct device_node *np, 5862306a36Sopenharmony_ci struct display_timing *dt) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci u32 val = 0; 6162306a36Sopenharmony_ci int ret = 0; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci memset(dt, 0, sizeof(*dt)); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci ret |= parse_timing_property(np, "hback-porch", &dt->hback_porch); 6662306a36Sopenharmony_ci ret |= parse_timing_property(np, "hfront-porch", &dt->hfront_porch); 6762306a36Sopenharmony_ci ret |= parse_timing_property(np, "hactive", &dt->hactive); 6862306a36Sopenharmony_ci ret |= parse_timing_property(np, "hsync-len", &dt->hsync_len); 6962306a36Sopenharmony_ci ret |= parse_timing_property(np, "vback-porch", &dt->vback_porch); 7062306a36Sopenharmony_ci ret |= parse_timing_property(np, "vfront-porch", &dt->vfront_porch); 7162306a36Sopenharmony_ci ret |= parse_timing_property(np, "vactive", &dt->vactive); 7262306a36Sopenharmony_ci ret |= parse_timing_property(np, "vsync-len", &dt->vsync_len); 7362306a36Sopenharmony_ci ret |= parse_timing_property(np, "clock-frequency", &dt->pixelclock); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci dt->flags = 0; 7662306a36Sopenharmony_ci if (!of_property_read_u32(np, "vsync-active", &val)) 7762306a36Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_VSYNC_HIGH : 7862306a36Sopenharmony_ci DISPLAY_FLAGS_VSYNC_LOW; 7962306a36Sopenharmony_ci if (!of_property_read_u32(np, "hsync-active", &val)) 8062306a36Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_HSYNC_HIGH : 8162306a36Sopenharmony_ci DISPLAY_FLAGS_HSYNC_LOW; 8262306a36Sopenharmony_ci if (!of_property_read_u32(np, "de-active", &val)) 8362306a36Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_DE_HIGH : 8462306a36Sopenharmony_ci DISPLAY_FLAGS_DE_LOW; 8562306a36Sopenharmony_ci if (!of_property_read_u32(np, "pixelclk-active", &val)) 8662306a36Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_PIXDATA_POSEDGE : 8762306a36Sopenharmony_ci DISPLAY_FLAGS_PIXDATA_NEGEDGE; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (!of_property_read_u32(np, "syncclk-active", &val)) 9062306a36Sopenharmony_ci dt->flags |= val ? DISPLAY_FLAGS_SYNC_POSEDGE : 9162306a36Sopenharmony_ci DISPLAY_FLAGS_SYNC_NEGEDGE; 9262306a36Sopenharmony_ci else if (dt->flags & (DISPLAY_FLAGS_PIXDATA_POSEDGE | 9362306a36Sopenharmony_ci DISPLAY_FLAGS_PIXDATA_NEGEDGE)) 9462306a36Sopenharmony_ci dt->flags |= dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE ? 9562306a36Sopenharmony_ci DISPLAY_FLAGS_SYNC_POSEDGE : 9662306a36Sopenharmony_ci DISPLAY_FLAGS_SYNC_NEGEDGE; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci if (of_property_read_bool(np, "interlaced")) 9962306a36Sopenharmony_ci dt->flags |= DISPLAY_FLAGS_INTERLACED; 10062306a36Sopenharmony_ci if (of_property_read_bool(np, "doublescan")) 10162306a36Sopenharmony_ci dt->flags |= DISPLAY_FLAGS_DOUBLESCAN; 10262306a36Sopenharmony_ci if (of_property_read_bool(np, "doubleclk")) 10362306a36Sopenharmony_ci dt->flags |= DISPLAY_FLAGS_DOUBLECLK; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (ret) { 10662306a36Sopenharmony_ci pr_err("%pOF: error reading timing properties\n", np); 10762306a36Sopenharmony_ci return -EINVAL; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci return 0; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/** 11462306a36Sopenharmony_ci * of_get_display_timing - parse a display_timing entry 11562306a36Sopenharmony_ci * @np: device_node with the timing subnode 11662306a36Sopenharmony_ci * @name: name of the timing node 11762306a36Sopenharmony_ci * @dt: display_timing struct to fill 11862306a36Sopenharmony_ci **/ 11962306a36Sopenharmony_ciint of_get_display_timing(const struct device_node *np, const char *name, 12062306a36Sopenharmony_ci struct display_timing *dt) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct device_node *timing_np; 12362306a36Sopenharmony_ci int ret; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (!np) 12662306a36Sopenharmony_ci return -EINVAL; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci timing_np = of_get_child_by_name(np, name); 12962306a36Sopenharmony_ci if (!timing_np) 13062306a36Sopenharmony_ci return -ENOENT; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci ret = of_parse_display_timing(timing_np, dt); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci of_node_put(timing_np); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci return ret; 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(of_get_display_timing); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci/** 14162306a36Sopenharmony_ci * of_get_display_timings - parse all display_timing entries from a device_node 14262306a36Sopenharmony_ci * @np: device_node with the subnodes 14362306a36Sopenharmony_ci **/ 14462306a36Sopenharmony_cistruct display_timings *of_get_display_timings(const struct device_node *np) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct device_node *timings_np; 14762306a36Sopenharmony_ci struct device_node *entry; 14862306a36Sopenharmony_ci struct device_node *native_mode; 14962306a36Sopenharmony_ci struct display_timings *disp; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (!np) 15262306a36Sopenharmony_ci return NULL; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci timings_np = of_get_child_by_name(np, "display-timings"); 15562306a36Sopenharmony_ci if (!timings_np) { 15662306a36Sopenharmony_ci pr_err("%pOF: could not find display-timings node\n", np); 15762306a36Sopenharmony_ci return NULL; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci disp = kzalloc(sizeof(*disp), GFP_KERNEL); 16162306a36Sopenharmony_ci if (!disp) { 16262306a36Sopenharmony_ci pr_err("%pOF: could not allocate struct disp'\n", np); 16362306a36Sopenharmony_ci goto dispfail; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci entry = of_parse_phandle(timings_np, "native-mode", 0); 16762306a36Sopenharmony_ci /* assume first child as native mode if none provided */ 16862306a36Sopenharmony_ci if (!entry) 16962306a36Sopenharmony_ci entry = of_get_next_child(timings_np, NULL); 17062306a36Sopenharmony_ci /* if there is no child, it is useless to go on */ 17162306a36Sopenharmony_ci if (!entry) { 17262306a36Sopenharmony_ci pr_err("%pOF: no timing specifications given\n", np); 17362306a36Sopenharmony_ci goto entryfail; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci pr_debug("%pOF: using %pOFn as default timing\n", np, entry); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci native_mode = entry; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci disp->num_timings = of_get_child_count(timings_np); 18162306a36Sopenharmony_ci if (disp->num_timings == 0) { 18262306a36Sopenharmony_ci /* should never happen, as entry was already found above */ 18362306a36Sopenharmony_ci pr_err("%pOF: no timings specified\n", np); 18462306a36Sopenharmony_ci goto entryfail; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci disp->timings = kcalloc(disp->num_timings, 18862306a36Sopenharmony_ci sizeof(struct display_timing *), 18962306a36Sopenharmony_ci GFP_KERNEL); 19062306a36Sopenharmony_ci if (!disp->timings) { 19162306a36Sopenharmony_ci pr_err("%pOF: could not allocate timings array\n", np); 19262306a36Sopenharmony_ci goto entryfail; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci disp->num_timings = 0; 19662306a36Sopenharmony_ci disp->native_mode = 0; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci for_each_child_of_node(timings_np, entry) { 19962306a36Sopenharmony_ci struct display_timing *dt; 20062306a36Sopenharmony_ci int r; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci dt = kmalloc(sizeof(*dt), GFP_KERNEL); 20362306a36Sopenharmony_ci if (!dt) { 20462306a36Sopenharmony_ci pr_err("%pOF: could not allocate display_timing struct\n", 20562306a36Sopenharmony_ci np); 20662306a36Sopenharmony_ci goto timingfail; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci r = of_parse_display_timing(entry, dt); 21062306a36Sopenharmony_ci if (r) { 21162306a36Sopenharmony_ci /* 21262306a36Sopenharmony_ci * to not encourage wrong devicetrees, fail in case of 21362306a36Sopenharmony_ci * an error 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_ci pr_err("%pOF: error in timing %d\n", 21662306a36Sopenharmony_ci np, disp->num_timings + 1); 21762306a36Sopenharmony_ci kfree(dt); 21862306a36Sopenharmony_ci goto timingfail; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (native_mode == entry) 22262306a36Sopenharmony_ci disp->native_mode = disp->num_timings; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci disp->timings[disp->num_timings] = dt; 22562306a36Sopenharmony_ci disp->num_timings++; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci of_node_put(timings_np); 22862306a36Sopenharmony_ci /* 22962306a36Sopenharmony_ci * native_mode points to the device_node returned by of_parse_phandle 23062306a36Sopenharmony_ci * therefore call of_node_put on it 23162306a36Sopenharmony_ci */ 23262306a36Sopenharmony_ci of_node_put(native_mode); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci pr_debug("%pOF: got %d timings. Using timing #%d as default\n", 23562306a36Sopenharmony_ci np, disp->num_timings, 23662306a36Sopenharmony_ci disp->native_mode + 1); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci return disp; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_citimingfail: 24162306a36Sopenharmony_ci of_node_put(native_mode); 24262306a36Sopenharmony_ci display_timings_release(disp); 24362306a36Sopenharmony_ci disp = NULL; 24462306a36Sopenharmony_cientryfail: 24562306a36Sopenharmony_ci kfree(disp); 24662306a36Sopenharmony_cidispfail: 24762306a36Sopenharmony_ci of_node_put(timings_np); 24862306a36Sopenharmony_ci return NULL; 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(of_get_display_timings); 251