| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * Copyright (C) 2012 ARM Limited |
| */ |
| |
| #define pr_fmt(fmt) "vexpress-dvi: " fmt |
| |
| #include <linux/fb.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/vexpress.h> |
| |
| |
| static struct vexpress_config_func *vexpress_dvimode_func; |
| |
| static struct { |
| u32 xres, yres, mode; |
| } vexpress_dvi_dvimodes[] = { |
| { 640, 480, 0 }, /* VGA */ |
| { 800, 600, 1 }, /* SVGA */ |
| { 1024, 768, 2 }, /* XGA */ |
| { 1280, 1024, 3 }, /* SXGA */ |
| { 1600, 1200, 4 }, /* UXGA */ |
| { 1920, 1080, 5 }, /* HD1080 */ |
| { 3840, 2160, 6 }, /* 4K - gem5 only */ |
| }; |
| |
| static void vexpress_dvi_mode_set(struct fb_info *info, u32 xres, u32 yres) |
| { |
| int err = -ENOENT; |
| int i; |
| |
| if (!vexpress_dvimode_func) |
| return; |
| |
| for (i = 0; i < ARRAY_SIZE(vexpress_dvi_dvimodes); i++) { |
| if (vexpress_dvi_dvimodes[i].xres == xres && |
| vexpress_dvi_dvimodes[i].yres == yres) { |
| pr_debug("mode: %ux%u = %d\n", xres, yres, |
| vexpress_dvi_dvimodes[i].mode); |
| err = vexpress_config_write(vexpress_dvimode_func, 0, |
| vexpress_dvi_dvimodes[i].mode); |
| break; |
| } |
| } |
| |
| if (err) |
| pr_warn("Failed to set %ux%u mode! (%d)\n", xres, yres, err); |
| } |
| |
| |
| static struct vexpress_config_func *vexpress_muxfpga_func; |
| static int vexpress_dvi_fb = -1; |
| |
| static int vexpress_dvi_mux_set(struct fb_info *info) |
| { |
| int err; |
| u32 site = vexpress_get_site_by_dev(info->device); |
| |
| if (!vexpress_muxfpga_func) |
| return -ENXIO; |
| |
| err = vexpress_config_write(vexpress_muxfpga_func, 0, site); |
| if (!err) { |
| pr_debug("Selected MUXFPGA input %d (fb%d)\n", site, |
| info->node); |
| vexpress_dvi_fb = info->node; |
| vexpress_dvi_mode_set(info, info->var.xres, |
| info->var.yres); |
| } else { |
| pr_warn("Failed to select MUXFPGA input %d (fb%d)! (%d)\n", |
| site, info->node, err); |
| } |
| |
| return err; |
| } |
| |
| static int vexpress_dvi_fb_select(int fb) |
| { |
| int err; |
| struct fb_info *info; |
| |
| /* fb0 is the default */ |
| if (fb < 0) |
| fb = 0; |
| |
| info = registered_fb[fb]; |
| if (!info || !lock_fb_info(info)) |
| return -ENODEV; |
| |
| err = vexpress_dvi_mux_set(info); |
| |
| unlock_fb_info(info); |
| |
| return err; |
| } |
| |
| static ssize_t vexpress_dvi_fb_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%d\n", vexpress_dvi_fb); |
| } |
| |
| static ssize_t vexpress_dvi_fb_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long value; |
| int err = kstrtol(buf, 0, &value); |
| |
| if (!err) |
| err = vexpress_dvi_fb_select(value); |
| |
| return err ? err : count; |
| } |
| |
| DEVICE_ATTR(fb, S_IRUGO | S_IWUSR, vexpress_dvi_fb_show, |
| vexpress_dvi_fb_store); |
| |
| |
| static int vexpress_dvi_fb_event_notify(struct notifier_block *self, |
| unsigned long action, void *data) |
| { |
| struct fb_event *event = data; |
| struct fb_info *info = event->info; |
| struct fb_videomode *mode = event->data; |
| |
| switch (action) { |
| case FB_EVENT_FB_REGISTERED: |
| if (vexpress_dvi_fb < 0) |
| vexpress_dvi_mux_set(info); |
| break; |
| case FB_EVENT_MODE_CHANGE: |
| case FB_EVENT_MODE_CHANGE_ALL: |
| if (info->node == vexpress_dvi_fb) |
| vexpress_dvi_mode_set(info, mode->xres, mode->yres); |
| break; |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block vexpress_dvi_fb_notifier = { |
| .notifier_call = vexpress_dvi_fb_event_notify, |
| }; |
| static bool vexpress_dvi_fb_notifier_registered; |
| |
| |
| enum vexpress_dvi_func { FUNC_MUXFPGA, FUNC_DVIMODE }; |
| |
| static struct of_device_id vexpress_dvi_of_match[] = { |
| { |
| .compatible = "arm,vexpress-muxfpga", |
| .data = (void *)FUNC_MUXFPGA, |
| }, { |
| .compatible = "arm,vexpress-dvimode", |
| .data = (void *)FUNC_DVIMODE, |
| }, |
| {} |
| }; |
| |
| static int vexpress_dvi_probe(struct platform_device *pdev) |
| { |
| enum vexpress_dvi_func func; |
| const struct of_device_id *match = |
| of_match_device(vexpress_dvi_of_match, &pdev->dev); |
| |
| if (match) |
| func = (enum vexpress_dvi_func)match->data; |
| else |
| func = pdev->id_entry->driver_data; |
| |
| switch (func) { |
| case FUNC_MUXFPGA: |
| vexpress_muxfpga_func = |
| vexpress_config_func_get_by_dev(&pdev->dev); |
| device_create_file(&pdev->dev, &dev_attr_fb); |
| break; |
| case FUNC_DVIMODE: |
| vexpress_dvimode_func = |
| vexpress_config_func_get_by_dev(&pdev->dev); |
| break; |
| } |
| |
| if (!vexpress_dvi_fb_notifier_registered) { |
| fb_register_client(&vexpress_dvi_fb_notifier); |
| vexpress_dvi_fb_notifier_registered = true; |
| } |
| |
| vexpress_dvi_fb_select(vexpress_dvi_fb); |
| |
| return 0; |
| } |
| |
| static const struct platform_device_id vexpress_dvi_id_table[] = { |
| { .name = "vexpress-muxfpga", .driver_data = FUNC_MUXFPGA, }, |
| { .name = "vexpress-dvimode", .driver_data = FUNC_DVIMODE, }, |
| {} |
| }; |
| |
| static struct platform_driver vexpress_dvi_driver = { |
| .probe = vexpress_dvi_probe, |
| .driver = { |
| .name = "vexpress-dvi", |
| .of_match_table = vexpress_dvi_of_match, |
| }, |
| .id_table = vexpress_dvi_id_table, |
| }; |
| |
| static int __init vexpress_dvi_init(void) |
| { |
| return platform_driver_register(&vexpress_dvi_driver); |
| } |
| device_initcall(vexpress_dvi_init); |