18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright 2011 Florian Tobias Schandinat <FlorianSchandinat@gmx.de>
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci/*
68c2ecf20Sopenharmony_ci * generic EDID driver
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/slab.h>
108c2ecf20Sopenharmony_ci#include <linux/fb.h>
118c2ecf20Sopenharmony_ci#include "via_aux.h"
128c2ecf20Sopenharmony_ci#include "../edid.h"
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_cistatic const char *name = "EDID";
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic void query_edid(struct via_aux_drv *drv)
198c2ecf20Sopenharmony_ci{
208c2ecf20Sopenharmony_ci	struct fb_monspecs *spec = drv->data;
218c2ecf20Sopenharmony_ci	unsigned char edid[EDID_LENGTH];
228c2ecf20Sopenharmony_ci	bool valid = false;
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci	if (spec) {
258c2ecf20Sopenharmony_ci		fb_destroy_modedb(spec->modedb);
268c2ecf20Sopenharmony_ci	} else {
278c2ecf20Sopenharmony_ci		spec = kmalloc(sizeof(*spec), GFP_KERNEL);
288c2ecf20Sopenharmony_ci		if (!spec)
298c2ecf20Sopenharmony_ci			return;
308c2ecf20Sopenharmony_ci	}
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	spec->version = spec->revision = 0;
338c2ecf20Sopenharmony_ci	if (via_aux_read(drv, 0x00, edid, EDID_LENGTH)) {
348c2ecf20Sopenharmony_ci		fb_edid_to_monspecs(edid, spec);
358c2ecf20Sopenharmony_ci		valid = spec->version || spec->revision;
368c2ecf20Sopenharmony_ci	}
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	if (!valid) {
398c2ecf20Sopenharmony_ci		kfree(spec);
408c2ecf20Sopenharmony_ci		spec = NULL;
418c2ecf20Sopenharmony_ci	} else
428c2ecf20Sopenharmony_ci		printk(KERN_DEBUG "EDID: %s %s\n", spec->manufacturer, spec->monitor);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	drv->data = spec;
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic const struct fb_videomode *get_preferred_mode(struct via_aux_drv *drv)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	struct fb_monspecs *spec = drv->data;
508c2ecf20Sopenharmony_ci	int i;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	if (!spec || !spec->modedb || !(spec->misc & FB_MISC_1ST_DETAIL))
538c2ecf20Sopenharmony_ci		return NULL;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	for (i = 0; i < spec->modedb_len; i++) {
568c2ecf20Sopenharmony_ci		if (spec->modedb[i].flag & FB_MODE_IS_FIRST &&
578c2ecf20Sopenharmony_ci			spec->modedb[i].flag & FB_MODE_IS_DETAILED)
588c2ecf20Sopenharmony_ci			return &spec->modedb[i];
598c2ecf20Sopenharmony_ci	}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	return NULL;
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic void cleanup(struct via_aux_drv *drv)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	struct fb_monspecs *spec = drv->data;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	if (spec)
698c2ecf20Sopenharmony_ci		fb_destroy_modedb(spec->modedb);
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_civoid via_aux_edid_probe(struct via_aux_bus *bus)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	struct via_aux_drv drv = {
758c2ecf20Sopenharmony_ci		.bus	=	bus,
768c2ecf20Sopenharmony_ci		.addr	=	0x50,
778c2ecf20Sopenharmony_ci		.name	=	name,
788c2ecf20Sopenharmony_ci		.cleanup	=	cleanup,
798c2ecf20Sopenharmony_ci		.get_preferred_mode	=	get_preferred_mode};
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	query_edid(&drv);
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/* as EDID devices can be connected/disconnected just add the driver */
848c2ecf20Sopenharmony_ci	via_aux_add(&drv);
858c2ecf20Sopenharmony_ci}
86