1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de> 4 */ 5 /* 6 * generic EDID driver 7 */ 8 9 #include <linux/slab.h> 10 #include <linux/fb.h> 11 #include "via_aux.h" 12 #include "../edid.h" 13 14 15 static const char *name = "EDID"; 16 17 18 static void query_edid(struct via_aux_drv *drv) 19 { 20 struct fb_monspecs *spec = drv->data; 21 unsigned char edid[EDID_LENGTH]; 22 bool valid = false; 23 24 if (spec) { 25 fb_destroy_modedb(spec->modedb); 26 } else { 27 spec = kmalloc(sizeof(*spec), GFP_KERNEL); 28 if (!spec) 29 return; 30 } 31 32 spec->version = spec->revision = 0; 33 if (via_aux_read(drv, 0x00, edid, EDID_LENGTH)) { 34 fb_edid_to_monspecs(edid, spec); 35 valid = spec->version || spec->revision; 36 } 37 38 if (!valid) { 39 kfree(spec); 40 spec = NULL; 41 } else 42 printk(KERN_DEBUG "EDID: %s %s\n", spec->manufacturer, spec->monitor); 43 44 drv->data = spec; 45 } 46 47 static const struct fb_videomode *get_preferred_mode(struct via_aux_drv *drv) 48 { 49 struct fb_monspecs *spec = drv->data; 50 int i; 51 52 if (!spec || !spec->modedb || !(spec->misc & FB_MISC_1ST_DETAIL)) 53 return NULL; 54 55 for (i = 0; i < spec->modedb_len; i++) { 56 if (spec->modedb[i].flag & FB_MODE_IS_FIRST && 57 spec->modedb[i].flag & FB_MODE_IS_DETAILED) 58 return &spec->modedb[i]; 59 } 60 61 return NULL; 62 } 63 64 static void cleanup(struct via_aux_drv *drv) 65 { 66 struct fb_monspecs *spec = drv->data; 67 68 if (spec) 69 fb_destroy_modedb(spec->modedb); 70 } 71 72 void via_aux_edid_probe(struct via_aux_bus *bus) 73 { 74 struct via_aux_drv drv = { 75 .bus = bus, 76 .addr = 0x50, 77 .name = name, 78 .cleanup = cleanup, 79 .get_preferred_mode = get_preferred_mode}; 80 81 query_edid(&drv); 82 83 /* as EDID devices can be connected/disconnected just add the driver */ 84 via_aux_add(&drv); 85 } 86