* Re: [PATCH 0/9] System Framebuffer Bus (sysfb)
From: Dave Airlie @ 2013-02-17 23:47 UTC (permalink / raw)
To: David Herrmann
Cc: linux-kernel, Florian Tobias Schandinat, linux-fbdev,
David Airlie, dri-devel
In-Reply-To: <CANq1E4TTB-Lp+eVRPpzOMnS7XNbzHJRTicwCcwXw3AUs11XFMw@mail.gmail.com>
>> I'm unsure if I like this or not, and I don't see why its greatly more
>> useful than the interface we have now.
>
> This interface at least solves the problem with having vesafb,
> uvesafb, vgacon, vga16fb, efifb, dvbe, defi and all other similar
> drivers from accessing the system framebuffer simultaneously. And
> provides a sane way of registering devices and drivers for it.
But do we have the problem now? or is it more when we get dvbe/defi?
Also vgacon is kinda different since fbcon kicks it off, not a driver,
> It also provides a way for real drivers to unload these drivers
> (sysfb_claim()) instead of using remove_conflicting_framebuffers(),
> which is horribly broken and has lots of race-conditions. (I tried
> fixing the fbdev refcounting/locking, but every time I tried, some
> driver broke because they worked around the bug. And fixing all
> drivers is just a lot of work...).
> And remove_conflicting_framebuffers() also doesn't work with
> vgacon/etc. which do _not_ use fbdev.
but vgacon is always kicked off by fbcon, so I'm not sure what etc there
is apart from it. I'd like to make fbcon a bool as well, so we don't
have to deal with it appearing after the drivers.
> We could simplify this approach by removing the bus and just providing
> the platform-device for vbefb/etc. from the arch-code. But I thought
> the bus-infrastructure allows nice rebinding of drivers if we need it
> at almost no cost.
I suspect a platform device is the right answer, since vesafb is that,
I think we should resolve vesafb vs dvbe by just making them not
config compatible, and dvbe vs defi seems like the same solution
as vesafb vs efi.
I'm just trying to work out what exactly you are fixing here, the
problems we have now don't seem to be addressed by this, if
it addresses future problems then it needs to be more upfront.
>> It doesn't solve the main problem with the current interface, which is
>> that if somebody opens has vesafb /dev/fb0, mmaps it, and modprobes a
>> real driver, things will fail either miserably or crappy from a users
>> pov.
>>
>> The internal reference counts stop vesafb from unloading due to the
>> mmaps, then i915 loads anyways and one bashes the other, or we fix so
>> i915 doesn't load and the user gets fail.
>
> It's not the mmap that prevents vesafb from unloading, it's the
> open-file instead. If a user does open(), mmap(), close(), they can
> still access the mapped memory but vesafb might get unloaded (this is,
> in fact, used by several user-space apps). So it's not about whether
> vesafb is still loaded, but rather what to do about users which have
> vesafb mmaped but don't expect it to go away.
In theory we'd have to do like GEM/TTM, and unmap_mapping_range for
all the open fd's mmaps and just point them to map something useless
or even just return -EFAULT, because really userspace needs to be told
something :-)
> So what do you propose to detect this case? Keep track of every user
> who mmap's vesafb? How can we detect when they unmap the memory? I
> think the only way to detect this is to wait for the pages'
> "mmap-count" to drop to zero and then release the memory.
>
> So lets compare this to other subsystems. If you unlink a file that is
> still mmaped, I think the file isn't removed from memory until the
> last user unmaps it. However, the memory-mapping is 'dead' in that it
> doesn't have any effect on real files.
> So why not copy that behavior to framebuffers? When a real DRM driver
> is loaded, simply reserve the VBE framebuffer-memory in VMEM until the
> last user unmaps it. But mark it 'dead' so it doesn't really belong to
> a _real_ framebuffer.
> So any access to the mmap'ed framebuffers will be a no-op as it just
> modifies 'dead' framebuffers.
>
> Another idea is copying the VBE framebuffer into the DRM driver so all
> old memory-maps are still valid. However, this prevents us from doing
> any kind of mode-setting in the DRM driver until the last fbdev user
> is gone (because there is no way to notify fbdev users of
> mode-setting).
> So in this case we are also stuck in a situation where we need to wait
> for all users to unmap their framebuffers.
It all kinda sucks, from the problems we've had previously with things
like plymouth
racing, (which all this will make much worse if we have kms vbe devices) really
what we want the system to do is give the user the proper driver asap, stalling
waiting for endless other things to let go is just going to screw
users, so I think
we need to be as upfront and brutal in the userspace interface if people mmaping
vesafb or efifb or any generic interface then expect to load a real driver, then
their old apps get killed.
> Any comments? If you have a plan on how it is supposed to work (or
> what the user-space semantics should be), tell me and I will try to
> make it work.
> I still think a central system-framebuffer registry like sysfb-bus
> (which does _not_ explicitly depend on fbdev) is the way to go.
> Whether it's a bus or not is just a matter of taste. I am willing to
> rework this.
As I said maybe I'm concentrating on the problem you aren't trying to fix,
but then I'm not sure I've enough information on the problem you are
trying to fix,
remove_confilicting_framebuffers might be ugly but it does 90% of what we want,
I just want to understand why this will make it better,
Dave.
^ permalink raw reply
* Re: [PATCH 0/9] System Framebuffer Bus (sysfb)
From: David Herrmann @ 2013-02-17 23:35 UTC (permalink / raw)
To: Dave Airlie
Cc: linux-fbdev, linux-kernel, dri-devel, Florian Tobias Schandinat
In-Reply-To: <CAPM=9txQtJ6FLFTEFr_Wi6RK=W9y3gKYxV_+zPCo6kLi4nM8eA@mail.gmail.com>
Hi Dave
On Sun, Feb 17, 2013 at 11:02 PM, Dave Airlie <airlied@gmail.com> wrote:
>>
>> This series tries to fix the mess with global system framebuffer access in
>> device drivers. Currently, architecture initialization sets the "screen_info"
>> object according to the system framebuffer that was detected during boot. The
>> device driver that can map VMEM first gets access to it. There is no way to give
>> a specific driver access to the device and no _proper_ way to revoke access
>> again. In fact, some drivers don't even check whether they mapped the VMEM
>> successfully, letting multiple drivers to access the system framebuffer at the
>> same time.
>
> I'm unsure if I like this or not, and I don't see why its greatly more
> useful than the interface we have now.
This interface at least solves the problem with having vesafb,
uvesafb, vgacon, vga16fb, efifb, dvbe, defi and all other similar
drivers from accessing the system framebuffer simultaneously. And
provides a sane way of registering devices and drivers for it.
It also provides a way for real drivers to unload these drivers
(sysfb_claim()) instead of using remove_conflicting_framebuffers(),
which is horribly broken and has lots of race-conditions. (I tried
fixing the fbdev refcounting/locking, but every time I tried, some
driver broke because they worked around the bug. And fixing all
drivers is just a lot of work...).
And remove_conflicting_framebuffers() also doesn't work with
vgacon/etc. which do _not_ use fbdev.
We could simplify this approach by removing the bus and just providing
the platform-device for vbefb/etc. from the arch-code. But I thought
the bus-infrastructure allows nice rebinding of drivers if we need it
at almost no cost.
You could even register an oops/panic-screen as driver there and
switch to it if necessary.
> It doesn't solve the main problem with the current interface, which is
> that if somebody opens has vesafb /dev/fb0, mmaps it, and modprobes a
> real driver, things will fail either miserably or crappy from a users
> pov.
>
> The internal reference counts stop vesafb from unloading due to the
> mmaps, then i915 loads anyways and one bashes the other, or we fix so
> i915 doesn't load and the user gets fail.
It's not the mmap that prevents vesafb from unloading, it's the
open-file instead. If a user does open(), mmap(), close(), they can
still access the mapped memory but vesafb might get unloaded (this is,
in fact, used by several user-space apps). So it's not about whether
vesafb is still loaded, but rather what to do about users which have
vesafb mmaped but don't expect it to go away.
So what do you propose to detect this case? Keep track of every user
who mmap's vesafb? How can we detect when they unmap the memory? I
think the only way to detect this is to wait for the pages'
"mmap-count" to drop to zero and then release the memory.
So lets compare this to other subsystems. If you unlink a file that is
still mmaped, I think the file isn't removed from memory until the
last user unmaps it. However, the memory-mapping is 'dead' in that it
doesn't have any effect on real files.
So why not copy that behavior to framebuffers? When a real DRM driver
is loaded, simply reserve the VBE framebuffer-memory in VMEM until the
last user unmaps it. But mark it 'dead' so it doesn't really belong to
a _real_ framebuffer.
So any access to the mmap'ed framebuffers will be a no-op as it just
modifies 'dead' framebuffers.
Another idea is copying the VBE framebuffer into the DRM driver so all
old memory-maps are still valid. However, this prevents us from doing
any kind of mode-setting in the DRM driver until the last fbdev user
is gone (because there is no way to notify fbdev users of
mode-setting).
So in this case we are also stuck in a situation where we need to wait
for all users to unmap their framebuffers.
Any comments? If you have a plan on how it is supposed to work (or
what the user-space semantics should be), tell me and I will try to
make it work.
I still think a central system-framebuffer registry like sysfb-bus
(which does _not_ explicitly depend on fbdev) is the way to go.
Whether it's a bus or not is just a matter of taste. I am willing to
rework this.
Thanks
David
^ permalink raw reply
* Re: [PATCH 0/9] System Framebuffer Bus (sysfb)
From: Dave Airlie @ 2013-02-17 22:02 UTC (permalink / raw)
To: David Herrmann
Cc: linux-fbdev, linux-kernel, dri-devel, Florian Tobias Schandinat
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
>
> This series tries to fix the mess with global system framebuffer access in
> device drivers. Currently, architecture initialization sets the "screen_info"
> object according to the system framebuffer that was detected during boot. The
> device driver that can map VMEM first gets access to it. There is no way to give
> a specific driver access to the device and no _proper_ way to revoke access
> again. In fact, some drivers don't even check whether they mapped the VMEM
> successfully, letting multiple drivers to access the system framebuffer at the
> same time.
I'm unsure if I like this or not, and I don't see why its greatly more
useful than the interface we have now.
It doesn't solve the main problem with the current interface, which is
that if somebody opens has vesafb /dev/fb0, mmaps it, and modprobes a
real driver, things will fail either miserably or crappy from a users
pov.
The internal reference counts stop vesafb from unloading due to the
mmaps, then i915 loads anyways and one bashes the other, or we fix so
i915 doesn't load and the user gets fail.
Dave.
^ permalink raw reply
* RE: [PATCH RFC] video: Add Hyper-V Synthetic Video Frame Buffer Driver
From: Haiyang Zhang @ 2013-02-17 19:10 UTC (permalink / raw)
To: Olaf Hering
Cc: FlorianSchandinat@gmx.de, linux-fbdev@vger.kernel.org,
KY Srinivasan, jasowang@redhat.com, linux-kernel@vger.kernel.org,
devel@linuxdriverproject.org
In-Reply-To: <20130217143223.GA11543@aepfle.de>
> From: Olaf Hering
> Sent: Sunday, February 17, 2013 9:32 AM
> To: Haiyang Zhang
> Cc: FlorianSchandinat@gmx.de; linux-fbdev@vger.kernel.org; KY Srinivasan; jasowang@redhat.com; linux-kernel@vger.kernel.org; devel@linuxdriverproject.org
> Subject: Re: [PATCH RFC] video: Add Hyper-V Synthetic Video Frame Buffer Driver
> On Fri, Feb 15, Haiyang Zhang wrote:
> +config HYPERV_FB
> This should probably be FB_HYPERB to follow the naming of all other
> drivers.
Will do.
Thanks,
- Haiyang
^ permalink raw reply
* [PATCH 9/9] drm: dvbe: add optional fbdev frontend
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
This adds a new fbdev frontend to the dvbe driver. It allows userspace to
access the dvbe driver via an fbdev frontend.
It is disabled by default so you can use dvbe without CONFIG_FB. It should
only be used for backwards-compatibility. Use the DRM dumb API to access
dvbe buffers properly.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
drivers/gpu/drm/dvbe/Kconfig | 18 +++
drivers/gpu/drm/dvbe/Makefile | 1 +
drivers/gpu/drm/dvbe/dvbe.h | 23 ++++
drivers/gpu/drm/dvbe/dvbe_fbdev.c | 235 ++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/dvbe/dvbe_main.c | 2 +
5 files changed, 279 insertions(+)
create mode 100644 drivers/gpu/drm/dvbe/dvbe_fbdev.c
diff --git a/drivers/gpu/drm/dvbe/Kconfig b/drivers/gpu/drm/dvbe/Kconfig
index e49df10..ca27455 100644
--- a/drivers/gpu/drm/dvbe/Kconfig
+++ b/drivers/gpu/drm/dvbe/Kconfig
@@ -27,3 +27,21 @@ config DRM_DVBE
To compile this driver as a module, choose M here: the
module will be called dvbe.
+
+config DRM_DVBE_FBDEV
+ bool "VESA BIOS Extension DRM fbdev Compatibility Layer"
+ depends on DRM_DVBE && FB
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ help
+ This provides an fbdev frontend (via /dev/fbX) for the DVBE VESA
+ driver. Old userspace that depends on the fbdev API can access the
+ DVBE driver this way. It provides full backwards compatibility to the
+ old vesafb driver.
+
+ Newer userspace accesses graphics devices via the DRM API and the old
+ fbdev compatibility layer is not needed. Activate it only if you
+ really need to run old userspace programs.
+
+ If unsure, say N.
diff --git a/drivers/gpu/drm/dvbe/Makefile b/drivers/gpu/drm/dvbe/Makefile
index f6fb888..e01aaa1 100644
--- a/drivers/gpu/drm/dvbe/Makefile
+++ b/drivers/gpu/drm/dvbe/Makefile
@@ -1,4 +1,5 @@
ccflags-y := -Iinclude/drm
dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o dvbe_vesa.o
+dvbe-$(CONFIG_DRM_DVBE_FBDEV) += dvbe_fbdev.o
obj-$(CONFIG_DRM_DVBE) := dvbe.o
diff --git a/drivers/gpu/drm/dvbe/dvbe.h b/drivers/gpu/drm/dvbe/dvbe.h
index 68fd452..dfe7c20 100644
--- a/drivers/gpu/drm/dvbe/dvbe.h
+++ b/drivers/gpu/drm/dvbe/dvbe.h
@@ -14,6 +14,7 @@
#define DVBE_DRV_H
#include <linux/errno.h>
+#include <linux/fb.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
@@ -47,6 +48,9 @@ struct dvbe_device {
struct drm_encoder enc;
struct drm_connector conn;
struct drm_display_mode *mode;
+
+ /* fbdev */
+ void *fbdev;
};
int dvbe_drm_load(struct drm_device *ddev, unsigned long flags);
@@ -95,4 +99,23 @@ int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb,
unsigned int flags, unsigned int color,
struct drm_clip_rect *clips, unsigned int num);
+/* fbdev helpers */
+
+#ifdef CONFIG_DRM_DVBE_FBDEV
+
+void dvbe_fbdev_init(struct dvbe_device *dvbe);
+void dvbe_fbdev_cleanup(struct dvbe_device *dvbe);
+
+#else /* CONFIG_DRM_DVBE_FBDEV */
+
+static inline void dvbe_fbdev_init(struct dvbe_device *dvbe)
+{
+}
+
+static inline void dvbe_fbdev_cleanup(struct dvbe_device *dvbe)
+{
+}
+
+#endif /* CONFIG_DRM_DVBE_FBDEV */
+
#endif /* DVBE_DRV_H */
diff --git a/drivers/gpu/drm/dvbe/dvbe_fbdev.c b/drivers/gpu/drm/dvbe/dvbe_fbdev.c
new file mode 100644
index 0000000..0e22e12
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_fbdev.c
@@ -0,0 +1,235 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * fbdev compatibility layer
+ * This provides an fbdev framebuffer device for the DVBE driver. It is
+ * based on the old vesafb.c driver:
+ * (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/fb.h>
+#include "dvbe.h"
+
+struct dvbe_fbdev {
+ struct dvbe_device *dvbe;
+ u32 palette[256];
+};
+
+static int dvbe_fbdev_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp,
+ struct fb_info *info)
+{
+ /*
+ * Set a single color register. The values supplied are
+ * already rounded down to the hardware's capabilities
+ * (according to the entries in the `var' structure). Return != 0 for
+ * invalid regno.
+ */
+
+ if (regno >= info->cmap.len)
+ return 1;
+ if (info->var.bits_per_pixel = 8)
+ return -EINVAL;
+ if (regno >= 16)
+ return 0;
+
+ switch (info->var.bits_per_pixel) {
+ case 16:
+ if (info->var.red.offset = 10) {
+ /* 1:5:5:5 */
+ ((u32*) (info->pseudo_palette))[regno] + ((red & 0xf800) >> 1) |
+ ((green & 0xf800) >> 6) |
+ ((blue & 0xf800) >> 11);
+ } else {
+ /* 0:5:6:5 */
+ ((u32*) (info->pseudo_palette))[regno] + ((red & 0xf800) ) |
+ ((green & 0xfc00) >> 5) |
+ ((blue & 0xf800) >> 11);
+ }
+ break;
+ case 24:
+ case 32:
+ red >>= 8;
+ green >>= 8;
+ blue >>= 8;
+ ((u32*) (info->pseudo_palette))[regno] + (red << info->var.red.offset) |
+ (green << info->var.green.offset) |
+ (blue << info->var.blue.offset);
+ break;
+ }
+
+ return 0;
+}
+
+static void dvbe_fbdev_free(struct dvbe_device *dvbe, struct fb_info *info)
+{
+ dev_info(dvbe->ddev->dev, "fbdev cleanup\n");
+ fb_dealloc_cmap(&info->cmap);
+ framebuffer_release(info);
+}
+
+static void dvbe_fbdev_destroy(struct fb_info *info)
+{
+ struct dvbe_fbdev *fb = info->par;
+ struct dvbe_device *dvbe = fb->dvbe;
+
+ mutex_lock(&dvbe->ddev->struct_mutex);
+ info = dvbe->fbdev;
+ dvbe->fbdev = NULL;
+ mutex_unlock(&dvbe->ddev->struct_mutex);
+
+ if (info)
+ dvbe_fbdev_free(dvbe, info);
+}
+
+static struct fb_ops dvbe_fbdev_ops = {
+ .owner = THIS_MODULE,
+ .fb_destroy = dvbe_fbdev_destroy,
+ .fb_setcolreg = dvbe_fbdev_setcolreg,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+};
+
+void dvbe_fbdev_init(struct dvbe_device *dvbe)
+{
+ struct dvbe_fbdev *fb;
+ struct fb_info *info;
+ int ret;
+
+ info = framebuffer_alloc(sizeof(struct dvbe_fbdev), dvbe->ddev->dev);
+ if (!info) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ fb = info->par;
+ fb->dvbe = dvbe;
+ info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE;
+ info->pseudo_palette = fb->palette;
+ info->screen_base = dvbe->vbe_map;
+ info->fbops = &dvbe_fbdev_ops;
+
+ strncpy(info->fix.id, "dvbedrmfb", 15);
+ info->fix.type = FB_TYPE_PACKED_PIXELS;
+ info->fix.accel = FB_ACCEL_NONE;
+ info->fix.smem_start = (unsigned long)dvbe->vbe_addr;
+ info->fix.smem_len = dvbe->vbe_size;
+ info->fix.line_length = dvbe->vbe_stride;
+
+ if (dvbe->vbe_bpp = 8)
+ info->fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
+ else
+ info->fix.visual = FB_VISUAL_TRUECOLOR;
+
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->var.vmode = FB_VMODE_NONINTERLACED;
+ info->var.bits_per_pixel = dvbe->vbe_bpp;
+ info->var.xres = dvbe->vbe_width;
+ info->var.yres = dvbe->vbe_height;
+ info->var.xres_virtual = info->var.xres;
+ info->var.yres_virtual = info->var.yres;
+
+ /* some dummy values for timing to make fbset happy */
+ info->var.pixclock = 10000000 / info->var.xres * 1000 / info->var.yres;
+ info->var.left_margin = (info->var.xres / 8) & 0xf8;
+ info->var.right_margin = 32;
+ info->var.upper_margin = 16;
+ info->var.lower_margin = 4;
+ info->var.hsync_len = (info->var.xres / 8) & 0xf8;
+ info->var.vsync_len = 4;
+
+ info->var.red.offset = dvbe->vbe_red_pos;
+ info->var.red.length = dvbe->vbe_red_size;
+ info->var.green.offset = dvbe->vbe_green_pos;
+ info->var.green.length = dvbe->vbe_green_size;
+ info->var.blue.offset = dvbe->vbe_blue_pos;
+ info->var.blue.length = dvbe->vbe_blue_size;
+
+ if (info->var.bits_per_pixel <= 8) {
+ info->var.red.length = info->var.bits_per_pixel;
+ info->var.green.length = info->var.bits_per_pixel;
+ info->var.blue.length = info->var.bits_per_pixel;
+ }
+
+ info->apertures = alloc_apertures(1);
+ if (!info->apertures) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+ info->apertures->ranges[0].base = (unsigned long)dvbe->vbe_addr;
+ info->apertures->ranges[0].size = dvbe->vbe_vsize;
+
+ ret = fb_alloc_cmap(&info->cmap, 256, 0);
+ if (ret < 0)
+ goto err_free;
+
+ /*
+ * TODO: Fix register_framebuffer() to not reset refcounts!
+ * So register_framebuffer() resets the refcnt of \info to 1. However,
+ * any other context might call remove_conflicting_framebuffers()
+ * before our call to register_framebuffer() returns.
+ * remove_conflicting_framebuffers() calls unregister_framebuffer()
+ * which then calls put_fb_info() and destroys \info by calling our
+ * fb_destroy() callback.
+ * To summarize, \info might be dead after register_framebuffer()
+ * returns so don't access it afterwards. There is _no_ reliable way to
+ * detect that so don't use \info at all now.
+ * Instead we lock all accesses around dvbe->fbdev and the first one who
+ * resets it is responsible of freeing it.
+ */
+
+ dvbe->fbdev = info;
+ ret = register_framebuffer(info);
+ if (ret < 0)
+ goto err_cmap;
+
+ mutex_lock(&dvbe->ddev->struct_mutex);
+ if (dvbe->fbdev)
+ dev_info(dvbe->ddev->dev, "fbdev frontend %s as fb%d\n",
+ info->fix.id, info->node);
+ mutex_unlock(&dvbe->ddev->struct_mutex);
+
+ return;
+
+err_cmap:
+ fb_dealloc_cmap(&info->cmap);
+err_free:
+ framebuffer_release(info);
+err_out:
+ dev_warn(dvbe->ddev->dev, "cannot create fbdev frontend (%d)\n", ret);
+}
+
+void dvbe_fbdev_cleanup(struct dvbe_device *dvbe)
+{
+ struct fb_info *info;
+
+ mutex_lock(&dvbe->ddev->struct_mutex);
+ info = dvbe->fbdev;
+ dvbe->fbdev = NULL;
+ mutex_unlock(&dvbe->ddev->struct_mutex);
+
+ if (!info)
+ return;
+
+ unregister_framebuffer(info);
+ dvbe_fbdev_free(dvbe, info);
+}
diff --git a/drivers/gpu/drm/dvbe/dvbe_main.c b/drivers/gpu/drm/dvbe/dvbe_main.c
index c418310..95241a6 100644
--- a/drivers/gpu/drm/dvbe/dvbe_main.c
+++ b/drivers/gpu/drm/dvbe/dvbe_main.c
@@ -408,6 +408,7 @@ int dvbe_drm_load(struct drm_device *ddev, unsigned long flags)
if (ret)
goto err_cleanup;
+ dvbe_fbdev_init(dvbe);
return 0;
err_cleanup:
@@ -422,6 +423,7 @@ int dvbe_drm_unload(struct drm_device *ddev)
{
struct dvbe_device *dvbe = ddev->dev_private;
+ dvbe_fbdev_cleanup(dvbe);
drm_mode_config_cleanup(ddev);
dvbe_vesa_cleanup(dvbe);
kfree(dvbe);
--
1.8.1.3
^ permalink raw reply related
* [PATCH 8/9] drm: dvbe: implement VBE/VESA blitting backend
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
Extend the dvbe core driver by a VESA/VBE backend that simply blits the
data from a framebuffer into the hardware framebuffer on damage.
Modesetting has to be done during the boot-process by the architecture
code (same way as vesafb requires it). No runtime modesetting is allowed
due to RealMode/ProtectedMode restrictions.
On dirty-ioctls we simply vmap the framebuffer memory and copy each pixel
into the target framebuffer. Unfortunately, the VBE bpp/depth combinations
cannot easily be forwarded to the user via the DRM API as it allows a lot
more combinations. Hence, we need to convert each pixel from the user's
buffer format into the target format while blitting.
Fast-paths for xrgb32/etc. could be implemented if we want to improve
blitting performance.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
drivers/gpu/drm/dvbe/Kconfig | 1 +
drivers/gpu/drm/dvbe/Makefile | 2 +-
drivers/gpu/drm/dvbe/dvbe.h | 25 ++++
drivers/gpu/drm/dvbe/dvbe_main.c | 39 +++++-
drivers/gpu/drm/dvbe/dvbe_vesa.c | 263 +++++++++++++++++++++++++++++++++++++++
5 files changed, 325 insertions(+), 5 deletions(-)
create mode 100644 drivers/gpu/drm/dvbe/dvbe_vesa.c
diff --git a/drivers/gpu/drm/dvbe/Kconfig b/drivers/gpu/drm/dvbe/Kconfig
index bb3aa7b..e49df10 100644
--- a/drivers/gpu/drm/dvbe/Kconfig
+++ b/drivers/gpu/drm/dvbe/Kconfig
@@ -3,6 +3,7 @@ config DRM_DVBE
depends on DRM
select DRM_KMS_HELPER
select DRM_SYSFB
+ select FB_BOOT_VESA_SUPPORT
help
This is a DRM/KMS driver for VESA BIOS Extension (VBE) compatible
cards. At least VBE 2.0 is needed. Older VBE 1.2 cards are not
diff --git a/drivers/gpu/drm/dvbe/Makefile b/drivers/gpu/drm/dvbe/Makefile
index b053da3..f6fb888 100644
--- a/drivers/gpu/drm/dvbe/Makefile
+++ b/drivers/gpu/drm/dvbe/Makefile
@@ -1,4 +1,4 @@
ccflags-y := -Iinclude/drm
-dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o
+dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o dvbe_vesa.o
obj-$(CONFIG_DRM_DVBE) := dvbe.o
diff --git a/drivers/gpu/drm/dvbe/dvbe.h b/drivers/gpu/drm/dvbe/dvbe.h
index 0235a95..68fd452 100644
--- a/drivers/gpu/drm/dvbe/dvbe.h
+++ b/drivers/gpu/drm/dvbe/dvbe.h
@@ -25,6 +25,23 @@
struct dvbe_device {
struct drm_device *ddev;
+ /* vbe information */
+ unsigned long vbe_addr;
+ unsigned long vbe_vsize;
+ unsigned long vbe_size;
+ unsigned int vbe_depth;
+ unsigned int vbe_bpp;
+ unsigned int vbe_width;
+ unsigned int vbe_height;
+ unsigned int vbe_stride;
+ uint8_t vbe_red_size;
+ uint8_t vbe_red_pos;
+ uint8_t vbe_green_size;
+ uint8_t vbe_green_pos;
+ uint8_t vbe_blue_size;
+ uint8_t vbe_blue_pos;
+ uint8_t *vbe_map;
+
/* mode-setting objects */
struct drm_crtc crtc;
struct drm_encoder enc;
@@ -70,4 +87,12 @@ struct dvbe_framebuffer {
#define to_dvbe_fb(x) container_of(x, struct dvbe_framebuffer, base)
+/* vesa helpers */
+
+int dvbe_vesa_init(struct dvbe_device *dvbe);
+void dvbe_vesa_cleanup(struct dvbe_device *dvbe);
+int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb,
+ unsigned int flags, unsigned int color,
+ struct drm_clip_rect *clips, unsigned int num);
+
#endif /* DVBE_DRV_H */
diff --git a/drivers/gpu/drm/dvbe/dvbe_main.c b/drivers/gpu/drm/dvbe/dvbe_main.c
index e73c77e..c418310 100644
--- a/drivers/gpu/drm/dvbe/dvbe_main.c
+++ b/drivers/gpu/drm/dvbe/dvbe_main.c
@@ -46,6 +46,15 @@ static bool dvbe_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
+ struct dvbe_device *dvbe = crtc->dev->dev_private;
+
+ if (mode->hdisplay != dvbe->vbe_width ||
+ mode->vdisplay != dvbe->vbe_height) {
+ dev_dbg(dvbe->ddev->dev, "invalid mode %ux%u\n",
+ mode->hdisplay, mode->vdisplay);
+ return false;
+ }
+
drm_mode_copy(adjusted_mode, mode);
return true;
}
@@ -66,6 +75,7 @@ static int dvbe_crtc_mode_set(struct drm_crtc *crtc,
int x, int y,
struct drm_framebuffer *old_fb)
{
+ struct dvbe_device *dvbe = crtc->dev->dev_private;
struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
/* We can scan out any framebuffer that is given. The framebuffer
@@ -79,7 +89,7 @@ static int dvbe_crtc_mode_set(struct drm_crtc *crtc,
if (x >= dfb->base.width || y >= dfb->base.height)
return -EINVAL;
- return 0;
+ return dvbe_vesa_damage(dvbe, dfb, 0, 0, NULL, 0);
}
/*
@@ -89,12 +99,13 @@ static int dvbe_crtc_mode_set(struct drm_crtc *crtc,
static int dvbe_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *fb)
{
+ struct dvbe_device *dvbe = crtc->dev->dev_private;
struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
if (x >= dfb->base.width || y >= dfb->base.height)
return -EINVAL;
- return 0;
+ return dvbe_vesa_damage(dvbe, dfb, 0, 0, NULL, 0);
}
static const struct drm_crtc_helper_funcs dvbe_crtc_helper_ops = {
@@ -159,7 +170,19 @@ static const struct drm_encoder_funcs dvbe_enc_ops = {
static int dvbe_conn_get_modes(struct drm_connector *conn)
{
- return 0;
+ struct dvbe_device *dvbe = conn->dev->dev_private;
+ struct drm_display_mode *mode;
+
+ mode = drm_gtf_mode(dvbe->ddev, dvbe->vbe_width, dvbe->vbe_height,
+ 60, 0, 0);
+ if (!mode)
+ return 0;
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(conn, mode);
+ dvbe->mode = mode;
+
+ return 1;
}
static int dvbe_conn_mode_valid(struct drm_connector *conn,
@@ -226,11 +249,12 @@ static int dvbe_fb_dirty(struct drm_framebuffer *fb, struct drm_file *file,
struct drm_clip_rect *clips, unsigned int num)
{
struct dvbe_device *dvbe = fb->dev->dev_private;
+ struct dvbe_framebuffer *dfb = to_dvbe_fb(fb);
if (dvbe->crtc.fb != fb)
return 0;
- return 0;
+ return dvbe_vesa_damage(dvbe, dfb, flags, color, clips, num);
}
static void dvbe_fb_destroy(struct drm_framebuffer *fb)
@@ -334,6 +358,10 @@ int dvbe_drm_load(struct drm_device *ddev, unsigned long flags)
dvbe->ddev = ddev;
ddev->dev_private = dvbe;
+ ret = dvbe_vesa_init(dvbe);
+ if (ret)
+ goto err_free;
+
drm_mode_config_init(ddev);
ddev->mode_config.min_width = 0;
ddev->mode_config.min_height = 0;
@@ -384,6 +412,8 @@ int dvbe_drm_load(struct drm_device *ddev, unsigned long flags)
err_cleanup:
drm_mode_config_cleanup(ddev);
+ dvbe_vesa_cleanup(dvbe);
+err_free:
kfree(dvbe);
return ret;
}
@@ -393,6 +423,7 @@ int dvbe_drm_unload(struct drm_device *ddev)
struct dvbe_device *dvbe = ddev->dev_private;
drm_mode_config_cleanup(ddev);
+ dvbe_vesa_cleanup(dvbe);
kfree(dvbe);
return 0;
diff --git a/drivers/gpu/drm/dvbe/dvbe_vesa.c b/drivers/gpu/drm/dvbe/dvbe_vesa.c
new file mode 100644
index 0000000..c3f96a0
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_vesa.c
@@ -0,0 +1,263 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * VESA BIOS Extension Layer
+ * This layer provides access to the VBE data for the dvbe driver. It reads the
+ * mode information from the initial boot screen_info and initializes the
+ * framebuffer for user-mode access.
+ *
+ * This driver requires the VESA mode to be a TRUECOLOR format with a bpp value
+ * of 8, 15, 16 or 32. All other layouts are unsupported.
+ */
+
+#include <asm/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/screen_info.h>
+#include <drm/drmP.h>
+#include "dvbe.h"
+
+static void dvbe_vesa_read(const uint8_t *src, unsigned int format,
+ uint8_t *r, uint8_t *g, uint8_t *b)
+{
+ uint32_t val;
+
+ switch (format) {
+ case DRM_FORMAT_RGB565:
+ val = *(uint16_t*)src;
+ *r = (val & 0xf800) >> 11;
+ *g = (val & 0x07e0) >> 5;
+ *b = (val & 0x001f) >> 0;
+ break;
+ case DRM_FORMAT_BGR565:
+ val = *(uint16_t*)src;
+ *b = (val & 0xf800) >> 11;
+ *g = (val & 0x07e0) >> 5;
+ *r = (val & 0x001f) >> 0;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ val = *(uint32_t*)src;
+ *r = (val & 0x00ff0000) >> 16;
+ *g = (val & 0x0000ff00) >> 8;
+ *b = (val & 0x000000ff) >> 0;
+ break;
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ val = *(uint32_t*)src;
+ *b = (val & 0x00ff0000) >> 16;
+ *g = (val & 0x0000ff00) >> 8;
+ *r = (val & 0x000000ff) >> 0;
+ break;
+ default:
+ *r = 0;
+ *g = 0;
+ *b = 0;
+ }
+}
+
+static void dvbe_vesa_write(struct dvbe_device *dvbe, uint8_t *dst,
+ uint8_t r, uint8_t g, uint8_t b)
+{
+ uint32_t val;
+
+ val = (r >> (8 - dvbe->vbe_red_size)) << dvbe->vbe_red_pos;
+ val |= (g >> (8 - dvbe->vbe_green_size)) << dvbe->vbe_green_pos;
+ val |= (b >> (8 - dvbe->vbe_blue_size)) << dvbe->vbe_blue_pos;
+
+ switch (dvbe->vbe_bpp) {
+ case 8:
+ *dst = val & 0xff;
+ break;
+ case 16:
+ *((uint16_t*)dst) = val & 0xffff;
+ break;
+ case 32:
+ *((uint32_t*)dst) = val & 0xffffffff;
+ break;
+ }
+}
+
+static void dvbe_vesa_blit(struct dvbe_device *dvbe, const uint8_t *src,
+ unsigned int src_stride, unsigned int src_f,
+ unsigned int src_bpp, unsigned int x, unsigned int y,
+ unsigned int width, unsigned int height)
+{
+ uint8_t *dst, *d, r, g, b;
+ const uint8_t *s;
+ unsigned int i, j, sBpp, dBpp;
+
+ sBpp = src_bpp / 8;
+ dBpp = dvbe->vbe_bpp / 8;
+ src = src + y * src_stride + x * sBpp;
+ dst = dvbe->vbe_map + y * dvbe->vbe_stride + x * dBpp;
+
+ for (i = 0; i < height; ++i) {
+ s = src;
+ d = dst;
+ for (j = 0; j < width; ++j) {
+ dvbe_vesa_read(src, src_f, &r, &g, &b);
+ dvbe_vesa_write(dvbe, d, r, g, b);
+ s += sBpp;
+ d += dBpp;
+ }
+
+ src += src_stride;
+ dst += dvbe->vbe_stride;
+ }
+}
+
+int dvbe_vesa_damage(struct dvbe_device *dvbe, struct dvbe_framebuffer *fb,
+ unsigned int flags, unsigned int color,
+ struct drm_clip_rect *clips, unsigned int num)
+{
+ unsigned int i, maxw, maxh;
+ unsigned int width, height, ret;
+ uint8_t *src;
+ bool annotated;
+
+ ret = dvbe_gem_vmap(fb->obj);
+ if (ret)
+ return ret;
+
+ annotated = flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY;
+ src = fb->obj->vmapping + fb->base.offsets[0];
+ maxw = min(fb->base.width, dvbe->vbe_width);
+ maxh = min(fb->base.height, dvbe->vbe_height);
+
+ if (!num) {
+ dvbe_vesa_blit(dvbe, src, fb->base.pitches[0],
+ fb->base.pixel_format, fb->base.bits_per_pixel,
+ 0, 0, maxw, maxh);
+ return 0;
+ }
+
+ for (i = 0; i < num; ++i) {
+ if (annotated && !(i & 0x1))
+ continue;
+ if (clips[i].x2 <= clips[i].x1 || clips[i].y2 <= clips[i].y1)
+ continue;
+
+ /* clip to framebuffer size */
+ if (clips[i].x1 >= maxw ||
+ clips[i].y1 >= maxh)
+ continue;
+ if (clips[i].x2 > maxw)
+ width = maxw - clips[i].x1;
+ else
+ width = clips[i].x2 - clips[i].x1;
+ if (clips[i].y2 > maxh)
+ height = maxh - clips[i].y1;
+ else
+ height = clips[i].y2 - clips[i].y1;
+
+ dvbe_vesa_blit(dvbe, src, fb->base.pitches[0],
+ fb->base.pixel_format, fb->base.bits_per_pixel,
+ clips[i].x1, clips[i].y1, width, height);
+ }
+
+ return 0;
+}
+
+int dvbe_vesa_init(struct dvbe_device *dvbe)
+{
+ int ret;
+
+ if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB) {
+ dev_info(dvbe->ddev->dev, "no VBE capable device found\n");
+ return -ENODEV;
+ }
+
+ dvbe->vbe_addr = (unsigned long)screen_info.lfb_base;
+ dvbe->vbe_width = screen_info.lfb_width;
+ dvbe->vbe_height = screen_info.lfb_height;
+ dvbe->vbe_stride = screen_info.lfb_linelength;
+ dvbe->vbe_depth = screen_info.lfb_depth;
+ dvbe->vbe_bpp = (dvbe->vbe_depth = 15) ? 16 : dvbe->vbe_depth;
+ dvbe->vbe_size = dvbe->vbe_height * dvbe->vbe_stride;
+ dvbe->vbe_vsize = screen_info.lfb_size * 0x10000;
+ if (dvbe->vbe_vsize < dvbe->vbe_size)
+ dvbe->vbe_vsize = dvbe->vbe_size;
+
+ dev_info(dvbe->ddev->dev, "VMEM at: %ld vsize: %ld rsize: %ld\n",
+ dvbe->vbe_addr, dvbe->vbe_vsize, dvbe->vbe_size);
+ dev_info(dvbe->ddev->dev, "width: %d height: %d stride: %d bpp: %d\n",
+ dvbe->vbe_width, dvbe->vbe_height, dvbe->vbe_stride,
+ dvbe->vbe_bpp);
+
+ if (dvbe->vbe_bpp != 8 && dvbe->vbe_bpp != 16 && dvbe->vbe_bpp != 32) {
+ dev_err(dvbe->ddev->dev, "unsupported bpp value %d\n",
+ dvbe->vbe_bpp);
+ return -ENODEV;
+ }
+ if (!screen_info.red_pos && !screen_info.green_pos &&
+ !screen_info.blue_pos) {
+ dev_err(dvbe->ddev->dev, "hardware not truecolor capable\n");
+ return -ENODEV;
+ }
+ if (!screen_info.red_size && !screen_info.green_size &&
+ !screen_info.blue_size) {
+ dev_err(dvbe->ddev->dev, "hardware not truecolor capable\n");
+ return -ENODEV;
+ }
+
+ dvbe->vbe_red_size = screen_info.red_size;
+ dvbe->vbe_red_pos = screen_info.red_pos;
+ dvbe->vbe_green_size = screen_info.green_size;
+ dvbe->vbe_green_pos = screen_info.green_pos;
+ dvbe->vbe_blue_size = screen_info.blue_size;
+ dvbe->vbe_blue_pos = screen_info.blue_pos;
+
+ dev_info(dvbe->ddev->dev, "color %d:%d r: %d:%d g: %d:%d b: %d:%d\n",
+ dvbe->vbe_depth, dvbe->vbe_bpp,
+ dvbe->vbe_red_pos, dvbe->vbe_red_size,
+ dvbe->vbe_green_pos, dvbe->vbe_green_size,
+ dvbe->vbe_blue_pos, dvbe->vbe_blue_size);
+
+ if (!request_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize, "dvbe")) {
+ dev_err(dvbe->ddev->dev, "cannot reserve VMEM\n");
+ return -EIO;
+ }
+
+ if (!request_region(0x3c0, 32, "dvbe")) {
+ dev_err(dvbe->ddev->dev, "cannot reserve VBIOS\n");
+ ret = -EIO;
+ goto err_mem_region;
+ }
+
+ dvbe->vbe_map = ioremap(dvbe->vbe_addr, dvbe->vbe_size);
+ if (!dvbe->vbe_map) {
+ dev_err(dvbe->ddev->dev, "cannot remap VMEM\n");
+ ret = -EIO;
+ goto err_region;
+ }
+
+ dev_info(dvbe->ddev->dev, "initialized VBE mode to %ux%u at %p\n",
+ dvbe->vbe_width, dvbe->vbe_height, dvbe->vbe_map);
+
+ return 0;
+
+err_region:
+ release_region(0x3c0, 32);
+err_mem_region:
+ release_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize);
+ return -ENODEV;
+}
+
+void dvbe_vesa_cleanup(struct dvbe_device *dvbe)
+{
+ dev_info(dvbe->ddev->dev, "VBE cleanup\n");
+ iounmap(dvbe->vbe_map);
+ release_region(0x3c0, 32);
+ release_mem_region(dvbe->vbe_addr, dvbe->vbe_vsize);
+}
--
1.8.1.3
^ permalink raw reply related
* [PATCH 7/9] drm: new VESA BIOS Extension DRM driver stub
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
This driver uses the VESA BIOS Extensions to control the display device.
Modesetting has to be done during the boot-process by the architecture
code (same way as vesafb requires it). No runtime modesetting is allowed
due to RealMode/ProtectedMode restrictions.
This patch only introduces the stub DRM driver without any VESA/VBE
backend that actually touches the hardware.
The driver simply provides a single crtc+encoder+connector combination
that user-space can use to access the VBE framebuffer. No page-flips are
supported and users must explicitly mark buffers as dirty to get them
copied into the framebuffer.
All buffer objects are backed by shmem so we can later add PRIME support.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/dvbe/Kconfig | 28 +++
drivers/gpu/drm/dvbe/Makefile | 4 +
drivers/gpu/drm/dvbe/dvbe.h | 73 +++++++
drivers/gpu/drm/dvbe/dvbe_drv.c | 104 ++++++++++
drivers/gpu/drm/dvbe/dvbe_main.c | 399 +++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/dvbe/dvbe_mem.c | 269 ++++++++++++++++++++++++++
8 files changed, 880 insertions(+)
create mode 100644 drivers/gpu/drm/dvbe/Kconfig
create mode 100644 drivers/gpu/drm/dvbe/Makefile
create mode 100644 drivers/gpu/drm/dvbe/dvbe.h
create mode 100644 drivers/gpu/drm/dvbe/dvbe_drv.c
create mode 100644 drivers/gpu/drm/dvbe/dvbe_main.c
create mode 100644 drivers/gpu/drm/dvbe/dvbe_mem.c
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 2df448e..fbbdabc 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -215,3 +215,5 @@ source "drivers/gpu/drm/mgag200/Kconfig"
source "drivers/gpu/drm/cirrus/Kconfig"
source "drivers/gpu/drm/shmobile/Kconfig"
+
+source "drivers/gpu/drm/dvbe/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 9249b66..ec91ae8 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -49,4 +49,5 @@ obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
+obj-$(CONFIG_DRM_DVBE) += dvbe/
obj-y += i2c/
diff --git a/drivers/gpu/drm/dvbe/Kconfig b/drivers/gpu/drm/dvbe/Kconfig
new file mode 100644
index 0000000..bb3aa7b
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/Kconfig
@@ -0,0 +1,28 @@
+config DRM_DVBE
+ tristate "VESA BIOS Extension DRM Driver"
+ depends on DRM
+ select DRM_KMS_HELPER
+ select DRM_SYSFB
+ help
+ This is a DRM/KMS driver for VESA BIOS Extension (VBE) compatible
+ cards. At least VBE 2.0 is needed. Older VBE 1.2 cards are not
+ supported.
+ Nearly all modern x86 graphics cards support VBE 2.0 so this driver
+ should work with all those graphics cards. However, it does not allow
+ mode-switching during runtime but requires the kernel to setup the
+ mode with the vga= kernel command line option. If you want full
+ support for your graphics card, please select a driver for your
+ specific model.
+
+ This driver can be enabled together with any other DRM graphics
+ driver. If another driver probes a device that conflicts with DVBE,
+ then DVBE will automatically drop the device and let the
+ model-specific driver take precedence.
+
+ This driver replaces the old vesafb framebuffer driver and provides
+ full backwards compatibility.
+
+ If unsure, say Y.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dvbe.
diff --git a/drivers/gpu/drm/dvbe/Makefile b/drivers/gpu/drm/dvbe/Makefile
new file mode 100644
index 0000000..b053da3
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/Makefile
@@ -0,0 +1,4 @@
+ccflags-y := -Iinclude/drm
+
+dvbe-y := dvbe_drv.o dvbe_main.o dvbe_mem.o
+obj-$(CONFIG_DRM_DVBE) := dvbe.o
diff --git a/drivers/gpu/drm/dvbe/dvbe.h b/drivers/gpu/drm/dvbe/dvbe.h
new file mode 100644
index 0000000..0235a95
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe.h
@@ -0,0 +1,73 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#ifndef DVBE_DRV_H
+#define DVBE_DRV_H
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+
+/* dvbe devices */
+
+struct dvbe_device {
+ struct drm_device *ddev;
+
+ /* mode-setting objects */
+ struct drm_crtc crtc;
+ struct drm_encoder enc;
+ struct drm_connector conn;
+ struct drm_display_mode *mode;
+};
+
+int dvbe_drm_load(struct drm_device *ddev, unsigned long flags);
+int dvbe_drm_unload(struct drm_device *ddev);
+int dvbe_drm_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* dvbe gem objects */
+
+struct dvbe_gem_object {
+ struct drm_gem_object base;
+ struct page **pages;
+ uint8_t *vmapping;
+};
+
+#define to_dvbe_bo(x) container_of(x, struct dvbe_gem_object, base)
+
+int dvbe_gem_init_object(struct drm_gem_object *obj);
+void dvbe_gem_free_object(struct drm_gem_object *obj);
+int dvbe_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
+int dvbe_gem_vmap(struct dvbe_gem_object *obj);
+void dvbe_gem_vunmap(struct dvbe_gem_object *obj);
+
+/* dumb buffers */
+
+int dvbe_dumb_create(struct drm_file *file_priv, struct drm_device *ddev,
+ struct drm_mode_create_dumb *arg);
+int dvbe_dumb_destroy(struct drm_file *file_priv, struct drm_device *ddev,
+ uint32_t handle);
+int dvbe_dumb_map_offset(struct drm_file *file_priv, struct drm_device *ddev,
+ uint32_t handle, uint64_t *offset);
+
+/* dvbe framebuffers */
+
+struct dvbe_framebuffer {
+ struct drm_framebuffer base;
+ struct dvbe_gem_object *obj;
+};
+
+#define to_dvbe_fb(x) container_of(x, struct dvbe_framebuffer, base)
+
+#endif /* DVBE_DRV_H */
diff --git a/drivers/gpu/drm/dvbe/dvbe_drv.c b/drivers/gpu/drm/dvbe/dvbe_drv.c
new file mode 100644
index 0000000..98d4d37
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_drv.c
@@ -0,0 +1,104 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/sysfb.h>
+#include <drm/drmP.h>
+#include <drm/drm_sysfb.h>
+#include "dvbe.h"
+
+static const struct vm_operations_struct dvbe_gem_vm_ops = {
+ .fault = dvbe_gem_fault,
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+};
+
+static const struct file_operations dvbe_drm_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .mmap = dvbe_drm_mmap,
+ .poll = drm_poll,
+ .read = drm_read,
+ .unlocked_ioctl = drm_ioctl,
+ .release = drm_release,
+ .fasync = drm_fasync,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .llseek = noop_llseek,
+};
+
+static struct drm_driver dvbe_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM,
+ .load = dvbe_drm_load,
+ .unload = dvbe_drm_unload,
+ .fops = &dvbe_drm_fops,
+
+ .gem_init_object = dvbe_gem_init_object,
+ .gem_free_object = dvbe_gem_free_object,
+ .gem_vm_ops = &dvbe_gem_vm_ops,
+
+ .dumb_create = dvbe_dumb_create,
+ .dumb_map_offset = dvbe_dumb_map_offset,
+ .dumb_destroy = dvbe_dumb_destroy,
+
+ .name = "dvbe",
+ .desc = "DRM VESA BIOS Extension Driver",
+ .date = "20130127",
+ .major = 0,
+ .minor = 0,
+ .patchlevel = 1,
+};
+
+static int dvbe_sysfb_probe(struct sysfb_device *sdev)
+{
+ return drm_get_sysfb_dev(sdev, &dvbe_drm_driver);
+}
+
+static void dvbe_sysfb_remove(struct sysfb_device *sdev)
+{
+ struct drm_device *ddev = dev_get_drvdata(&sdev->dev);
+
+ drm_unplug_dev(ddev);
+}
+
+static struct sysfb_driver dvbe_sysfb_driver = {
+ .type_mask = SYSFB_VBE,
+ .allow_tainted = false,
+ .driver = {
+ .name = "dvbe",
+ .owner = THIS_MODULE,
+ .mod_name = KBUILD_MODNAME,
+ },
+ .probe = dvbe_sysfb_probe,
+ .remove = dvbe_sysfb_remove,
+};
+
+static int __init dvbe_init(void)
+{
+ return drm_sysfb_init(&dvbe_drm_driver, &dvbe_sysfb_driver);
+}
+
+static void __exit dvbe_exit(void)
+{
+ drm_sysfb_exit(&dvbe_drm_driver, &dvbe_sysfb_driver);
+}
+
+module_init(dvbe_init);
+module_exit(dvbe_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("DRM VESA BIOS Extension Driver");
diff --git a/drivers/gpu/drm/dvbe/dvbe_main.c b/drivers/gpu/drm/dvbe/dvbe_main.c
new file mode 100644
index 0000000..e73c77e
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_main.c
@@ -0,0 +1,399 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * Main Modesetting and Control
+ * We register exactly one CRTC, encoder and connector on initialization. This
+ * simplifies the logic a lot. The actual device control is not implemented
+ * here. This file only provides the interaction with DRM core.
+ *
+ * Userspace has only one valid CRTC+encoder+connector combination which
+ * corresponds to the VESA framebuffer that we detected. The initial mode is
+ * deduced from the initial VESA configuration and should be used by user-space.
+ * Real mode-setting (i.e., changing display resolution) requires the processor
+ * to be in RealMode, which we cannot do in the kernel so it is not provided.
+ * Driver-specific ioctls may be used to allow userspace to perform mode-setting
+ * themself.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include "dvbe.h"
+
+/* crtcs */
+
+static void dvbe_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ /* There is no way to tell DRM core that we do not support DPMS.
+ * Therefore, simply make this a no-op and always be online. */
+}
+
+static bool dvbe_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ drm_mode_copy(adjusted_mode, mode);
+ return true;
+}
+
+static void dvbe_crtc_prepare(struct drm_crtc *crtc)
+{
+ /* nothing to prepare */
+}
+
+static void dvbe_crtc_commit(struct drm_crtc *crtc)
+{
+ /* all work already done by immediate mode-set */
+}
+
+static int dvbe_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
+
+ /* We can scan out any framebuffer that is given. The framebuffer
+ * allocation guarantees that it is a valid framebuffer. If the x/y
+ * corrdinates are out of bounds, we fail. We don't care whether the
+ * framebuffer is bigger/smaller than the real screen. The
+ * blit-functions clip it correctly.
+ * The mode was already checked by mode_fixup above so we can savely let
+ * it pass. */
+
+ if (x >= dfb->base.width || y >= dfb->base.height)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * Shortcut if no full mode-set is needed but only the offsets or framebuffer
+ * parameters changed. See @mode_set for details.
+ */
+static int dvbe_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *fb)
+{
+ struct dvbe_framebuffer *dfb = to_dvbe_fb(crtc->fb);
+
+ if (x >= dfb->base.width || y >= dfb->base.height)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct drm_crtc_helper_funcs dvbe_crtc_helper_ops = {
+ .dpms = dvbe_crtc_dpms,
+ .mode_fixup = dvbe_crtc_mode_fixup,
+ .prepare = dvbe_crtc_prepare,
+ .commit = dvbe_crtc_commit,
+ .mode_set = dvbe_crtc_mode_set,
+ .mode_set_base = dvbe_crtc_mode_set_base,
+};
+
+static const struct drm_crtc_funcs dvbe_crtc_ops = {
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_crtc_helper_set_config,
+};
+
+/* encoders */
+
+static void dvbe_enc_dpms(struct drm_encoder *enc, int mode)
+{
+ /* DPMS is done by the CRTC */
+}
+
+static bool dvbe_enc_mode_fixup(struct drm_encoder *enc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* mode-fixup is done by the CRTC */
+ return true;
+}
+
+static void dvbe_enc_prepare(struct drm_encoder *enc)
+{
+ /* no preparation to do */
+}
+
+static void dvbe_enc_mode_set(struct drm_encoder *enc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* mode-setting is done by the CRTC */
+}
+
+static void dvbe_enc_commit(struct drm_encoder *enc)
+{
+ /* committing mode changes is done by the CRTC */
+}
+
+static const struct drm_encoder_helper_funcs dvbe_enc_helper_ops = {
+ .dpms = dvbe_enc_dpms,
+ .mode_fixup = dvbe_enc_mode_fixup,
+ .prepare = dvbe_enc_prepare,
+ .mode_set = dvbe_enc_mode_set,
+ .commit = dvbe_enc_commit,
+};
+
+static const struct drm_encoder_funcs dvbe_enc_ops = {
+ .destroy = drm_encoder_cleanup,
+};
+
+/* connectors */
+
+static int dvbe_conn_get_modes(struct drm_connector *conn)
+{
+ return 0;
+}
+
+static int dvbe_conn_mode_valid(struct drm_connector *conn,
+ struct drm_display_mode *mode)
+{
+ /* every mode of our own is valid */
+ return MODE_OK;
+}
+
+static struct drm_encoder *dvbe_conn_best_encoder(struct drm_connector *conn)
+{
+ struct dvbe_device *dvbe = conn->dev->dev_private;
+
+ /* We only have a single encoder for the VBE device. Hence, return it
+ * as best-encoder so it is always used. There is no real encoder that
+ * we control as VBE doesn't provide such information. */
+ return &dvbe->enc;
+}
+
+static const struct drm_connector_helper_funcs dvbe_conn_helper_ops = {
+ .get_modes = dvbe_conn_get_modes,
+ .mode_valid = dvbe_conn_mode_valid,
+ .best_encoder = dvbe_conn_best_encoder,
+};
+
+static enum drm_connector_status dvbe_conn_detect(struct drm_connector *conn,
+ bool force)
+{
+ /* We simulate an always connected monitor. The VESA ABI doesn't
+ * provide any way to detect whether the connector is active. Hence,
+ * signal DRM core that it is always connected. */
+ return connector_status_connected;
+}
+
+static void dvbe_conn_destroy(struct drm_connector *conn)
+{
+ /* Remove the fake-connector from sysfs and then let the DRM core
+ * clean up all associated resources. */
+ if (device_is_registered(&conn->kdev))
+ drm_sysfs_connector_remove(conn);
+ drm_connector_cleanup(conn);
+}
+
+static const struct drm_connector_funcs dvbe_conn_ops = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = dvbe_conn_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = dvbe_conn_destroy,
+};
+
+/* framebuffers */
+
+static int dvbe_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *dfile,
+ unsigned int *handle)
+{
+ struct dvbe_framebuffer *dfb = to_dvbe_fb(fb);
+
+ return drm_gem_handle_create(dfile, &dfb->obj->base, handle);
+}
+
+static int dvbe_fb_dirty(struct drm_framebuffer *fb, struct drm_file *file,
+ unsigned int flags, unsigned int color,
+ struct drm_clip_rect *clips, unsigned int num)
+{
+ struct dvbe_device *dvbe = fb->dev->dev_private;
+
+ if (dvbe->crtc.fb != fb)
+ return 0;
+
+ return 0;
+}
+
+static void dvbe_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct dvbe_framebuffer *dfb = to_dvbe_fb(fb);
+
+ drm_framebuffer_cleanup(fb);
+ drm_gem_object_unreference_unlocked(&dfb->obj->base);
+ kfree(dfb);
+}
+
+static const struct drm_framebuffer_funcs dvbe_fb_ops = {
+ .create_handle = dvbe_fb_create_handle,
+ .dirty = dvbe_fb_dirty,
+ .destroy = dvbe_fb_destroy,
+};
+
+static struct drm_framebuffer *dvbe_fb_create(struct drm_device *ddev,
+ struct drm_file *dfile,
+ struct drm_mode_fb_cmd2 *cmd)
+{
+ struct dvbe_framebuffer *dfb;
+ int ret;
+ struct drm_gem_object *dobj;
+ unsigned int Bpp;
+ size_t siz;
+ void *err;
+
+ if (cmd->flags)
+ return ERR_PTR(-EINVAL);
+
+ switch (cmd->pixel_format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ Bpp = 2;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ Bpp = 4;
+ break;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+
+ siz = cmd->pitches[0] * (cmd->height - 1) +
+ Bpp * cmd->width + cmd->offsets[0];
+
+ dobj = drm_gem_object_lookup(ddev, dfile, cmd->handles[0]);
+ if (!dobj)
+ return ERR_PTR(-EINVAL);
+ if (dobj->size < siz) {
+ err = ERR_PTR(-ENOMEM);
+ goto err_unref;
+ }
+
+ dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
+ if (!dfb) {
+ err = ERR_PTR(-ENOMEM);
+ goto err_unref;
+ }
+ dfb->obj = to_dvbe_bo(dobj);
+
+ ret = drm_helper_mode_fill_fb_struct(&dfb->base, cmd);
+ if (ret < 0) {
+ err = ERR_PTR(ret);
+ goto err_free;
+ }
+
+ ret = drm_framebuffer_init(ddev, &dfb->base, &dvbe_fb_ops);
+ if (ret < 0) {
+ err = ERR_PTR(ret);
+ goto err_free;
+ }
+
+ return &dfb->base;
+
+err_free:
+ kfree(dfb);
+err_unref:
+ drm_gem_object_unreference_unlocked(dobj);
+ return err;
+}
+
+static const struct drm_mode_config_funcs dvbe_mode_config_ops = {
+ .fb_create = dvbe_fb_create,
+};
+
+/* initialization */
+
+int dvbe_drm_load(struct drm_device *ddev, unsigned long flags)
+{
+ struct dvbe_device *dvbe;
+ int ret;
+
+ dvbe = kzalloc(sizeof(*dvbe), GFP_KERNEL);
+ if (!dvbe)
+ return -ENOMEM;
+
+ dvbe->ddev = ddev;
+ ddev->dev_private = dvbe;
+
+ drm_mode_config_init(ddev);
+ ddev->mode_config.min_width = 0;
+ ddev->mode_config.min_height = 0;
+ ddev->mode_config.max_width = 4095;
+ ddev->mode_config.max_height = 4095;
+ ddev->mode_config.funcs = &dvbe_mode_config_ops;
+
+ ret = drm_mode_create_dirty_info_property(ddev);
+ if (ret)
+ goto err_cleanup;
+
+ ret = drm_crtc_init(ddev, &dvbe->crtc, &dvbe_crtc_ops);
+ if (ret)
+ goto err_cleanup;
+ drm_crtc_helper_add(&dvbe->crtc, &dvbe_crtc_helper_ops);
+
+ dvbe->enc.possible_crtcs = 1;
+ dvbe->enc.possible_clones = 0;
+ ret = drm_encoder_init(ddev, &dvbe->enc, &dvbe_enc_ops,
+ DRM_MODE_ENCODER_VIRTUAL);
+ if (ret)
+ goto err_cleanup;
+ drm_encoder_helper_add(&dvbe->enc, &dvbe_enc_helper_ops);
+
+ dvbe->conn.display_info.width_mm = 0;
+ dvbe->conn.display_info.height_mm = 0;
+ dvbe->conn.interlace_allowed = false;
+ dvbe->conn.doublescan_allowed = false;
+ dvbe->conn.polled = 0;
+ ret = drm_connector_init(ddev, &dvbe->conn, &dvbe_conn_ops,
+ DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret)
+ goto err_cleanup;
+ drm_connector_helper_add(&dvbe->conn, &dvbe_conn_helper_ops);
+
+ drm_object_attach_property(&dvbe->conn.base,
+ ddev->mode_config.dirty_info_property, 1);
+
+ ret = drm_mode_connector_attach_encoder(&dvbe->conn, &dvbe->enc);
+ if (ret)
+ goto err_cleanup;
+
+ ret = drm_sysfs_connector_add(&dvbe->conn);
+ if (ret)
+ goto err_cleanup;
+
+ return 0;
+
+err_cleanup:
+ drm_mode_config_cleanup(ddev);
+ kfree(dvbe);
+ return ret;
+}
+
+int dvbe_drm_unload(struct drm_device *ddev)
+{
+ struct dvbe_device *dvbe = ddev->dev_private;
+
+ drm_mode_config_cleanup(ddev);
+ kfree(dvbe);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/dvbe/dvbe_mem.c b/drivers/gpu/drm/dvbe/dvbe_mem.c
new file mode 100644
index 0000000..211aebf
--- /dev/null
+++ b/drivers/gpu/drm/dvbe/dvbe_mem.c
@@ -0,0 +1,269 @@
+/*
+ * DRM VESA BIOS Extension Driver
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * DVBA Memory Objects
+ * Due to the limited functionality of the VESA ABI, we support only one memory
+ * object type which is the framebuffer object. To allow file-descriptors for
+ * frambuffers we use shmem storage for all gem objects.
+ *
+ * Furthermore, we use lazy storage allocation. As long as the storage isn't
+ * mapped or accessed, we do not allocate it. If it is mapped, we allocate an
+ * array of pointers to the pages so we can easily vmap/vunmap it for CPU
+ * access.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/shmem_fs.h>
+#include <linux/string.h>
+#include <drm/drmP.h>
+#include <drm/drm_mem_util.h>
+#include "dvbe.h"
+
+static struct dvbe_gem_object *dvbe_gem_alloc_object(struct drm_device *ddev,
+ size_t size)
+{
+ struct dvbe_gem_object *obj;
+
+ BUG_ON((size & (PAGE_SIZE - 1)) != 0);
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return NULL;
+
+ if (drm_gem_object_init(ddev, &obj->base, size)) {
+ kfree(obj);
+ return NULL;
+ }
+
+ return obj;
+}
+
+/*
+ * First time user-space requests access to a gem object, we allocate an array
+ * of all backing pages so our page-fault handler can map them efficiently.
+ * shmem_read_mapping_page_gfp allocates missing pages with the given GFP mask
+ * which we deduce from the tmpfs-dentry object.
+ */
+static int dvbe_gem_get_pages(struct dvbe_gem_object *obj)
+{
+ size_t i, page_count = obj->base.size / PAGE_SIZE;
+ struct page *page;
+ struct address_space *mapping;
+ gfp_t gfp;
+
+ if (obj->pages)
+ return 0;
+
+ obj->pages = drm_malloc_ab(page_count, sizeof(struct page*));
+ if (!obj->pages)
+ return -ENOMEM;
+
+ mapping = obj->base.filp->f_path.dentry->d_inode->i_mapping;
+ gfp = mapping_gfp_mask(mapping) | GFP_KERNEL;
+
+ for (i = 0; i < page_count; ++i) {
+ page = shmem_read_mapping_page_gfp(mapping, i, gfp);
+ if (IS_ERR(page))
+ goto err_pages;
+
+ obj->pages[i] = page;
+ }
+
+ return 0;
+
+err_pages:
+ while (i--)
+ page_cache_release(obj->pages[i]);
+ drm_free_large(obj->pages);
+ obj->pages = NULL;
+ return PTR_ERR(page);
+}
+
+static void dvbe_gem_put_pages(struct dvbe_gem_object *obj)
+{
+ size_t i, page_count = obj->base.size / PAGE_SIZE;
+
+ if (!obj->pages)
+ return;
+
+ for (i = 0; i < page_count; ++i)
+ page_cache_release(obj->pages[i]);
+
+ drm_free_large(obj->pages);
+ obj->pages = NULL;
+}
+
+int dvbe_gem_vmap(struct dvbe_gem_object *obj)
+{
+ int ret, page_count = obj->base.size / PAGE_SIZE;
+
+ if (obj->vmapping)
+ return 0;
+
+ ret = dvbe_gem_get_pages(obj);
+ if (ret)
+ return ret;
+
+ obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
+ if (!obj->vmapping)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void dvbe_gem_vunmap(struct dvbe_gem_object *obj)
+{
+ if (!obj->vmapping)
+ return;
+
+ vunmap(obj->vmapping);
+ obj->vmapping = NULL;
+}
+
+/* drm_gem_object_alloc() is not supported */
+int dvbe_gem_init_object(struct drm_gem_object *gobj)
+{
+ BUG();
+
+ return -EINVAL;
+}
+
+void dvbe_gem_free_object(struct drm_gem_object *gobj)
+{
+ struct dvbe_gem_object *obj = to_dvbe_bo(gobj);
+
+ if (gobj->map_list.map)
+ drm_gem_free_mmap_offset(gobj);
+ dvbe_gem_vunmap(obj);
+ dvbe_gem_put_pages(obj);
+ drm_gem_object_release(gobj);
+ kfree(obj);
+}
+
+/*
+ * On page-faults we need to calculate the page-offset ourself as the vma-offset
+ * is based on the fake-offset of drm_mmap() instead of any real offset.
+ * We simply insert the page via vm_insert_page() as it has been marked as
+ * VM_MIXEDMAP in the vma.
+ */
+int dvbe_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ struct dvbe_gem_object *obj = to_dvbe_bo(vma->vm_private_data);
+ unsigned long off, vaddr;
+ int ret;
+
+ if (!obj->pages)
+ return VM_FAULT_SIGBUS;
+
+ vaddr = (unsigned long)vmf->virtual_address;
+ off = (vaddr - vma->vm_start) >> PAGE_SHIFT;
+
+ ret = vm_insert_page(vma, vaddr, obj->pages[off]);
+ switch (ret) {
+ case -ENOMEM:
+ return VM_FAULT_OOM;
+ case -EAGAIN:
+ set_need_resched();
+ /* fallthrough */
+ case -ERESTARTSYS:
+ case 0:
+ return VM_FAULT_NOPAGE;
+ }
+
+ return VM_FAULT_SIGBUS;
+}
+
+int dvbe_dumb_create(struct drm_file *dfile, struct drm_device *ddev,
+ struct drm_mode_create_dumb *args)
+{
+ struct dvbe_gem_object *obj;
+ int ret;
+
+ args->pitch = args->width * ((args->bpp + 7) / 8);
+ args->size = args->pitch * args->height;
+
+ if (!args->size)
+ return -EINVAL;
+
+ args->size = roundup(args->size, PAGE_SIZE);
+ obj = dvbe_gem_alloc_object(ddev, args->size);
+ if (!obj)
+ return -ENOMEM;
+
+ ret = drm_gem_handle_create(dfile, &obj->base, &args->handle);
+ if (ret) {
+ drm_gem_object_unreference(&obj->base);
+ return ret;
+ }
+
+ drm_gem_object_unreference(&obj->base);
+ return 0;
+}
+
+int dvbe_dumb_destroy(struct drm_file *dfile, struct drm_device *ddev,
+ uint32_t handle)
+{
+ return drm_gem_handle_delete(dfile, handle);
+}
+
+int dvbe_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev,
+ uint32_t handle, uint64_t *offset)
+{
+ struct dvbe_gem_object *obj;
+ struct drm_gem_object *gobj;
+ int ret;
+
+ mutex_lock(&ddev->struct_mutex);
+ gobj = drm_gem_object_lookup(ddev, dfile, handle);
+ if (!gobj) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+ obj = to_dvbe_bo(gobj);
+
+ ret = dvbe_gem_get_pages(obj);
+ if (ret)
+ goto out_unref;
+
+ if (!gobj->map_list.map) {
+ ret = drm_gem_create_mmap_offset(gobj);
+ if (ret)
+ goto out_unref;
+ }
+
+ *offset = gobj->map_list.hash.key << PAGE_SHIFT;
+
+out_unref:
+ drm_gem_object_unreference(gobj);
+out_unlock:
+ mutex_unlock(&ddev->struct_mutex);
+ return ret;
+}
+
+int dvbe_drm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int ret;
+
+ ret = drm_gem_mmap(filp, vma);
+ if (ret)
+ return ret;
+
+ vma->vm_flags &= ~VM_PFNMAP;
+ vma->vm_flags |= VM_MIXEDMAP;
+ vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
+
+ return ret;
+}
--
1.8.1.3
^ permalink raw reply related
* [PATCH 6/9] drm: new sysfb DRM bus module
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
This provides a new DRM bus helper for the system framebuffer bus. It is
very similar in its functionality to the DRM_USB helper. It allows to
write DRM drivers that register as SYSFB drivers to the system.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
drivers/gpu/drm/Kconfig | 5 ++
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/drm_sysfb.c | 145 ++++++++++++++++++++++++++++++++++++++++++++
include/drm/drmP.h | 4 ++
include/drm/drm_sysfb.h | 35 +++++++++++
5 files changed, 190 insertions(+)
create mode 100644 drivers/gpu/drm/drm_sysfb.c
create mode 100644 include/drm/drm_sysfb.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 18321b68b..2df448e 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -25,6 +25,11 @@ config DRM_USB
depends on USB_SUPPORT && USB_ARCH_HAS_HCD
select USB
+config DRM_SYSFB
+ tristate
+ depends on DRM
+ select SYSFB
+
config DRM_KMS_HELPER
tristate
depends on DRM
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 2ff5cef..9249b66 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -29,6 +29,7 @@ CFLAGS_drm_trace_points.o := -I$(src)
obj-$(CONFIG_DRM) += drm.o
obj-$(CONFIG_DRM_USB) += drm_usb.o
+obj-$(CONFIG_DRM_SYSFB) += drm_sysfb.o
obj-$(CONFIG_DRM_TTM) += ttm/
obj-$(CONFIG_DRM_TDFX) += tdfx/
obj-$(CONFIG_DRM_R128) += r128/
diff --git a/drivers/gpu/drm/drm_sysfb.c b/drivers/gpu/drm/drm_sysfb.c
new file mode 100644
index 0000000..4e8a823
--- /dev/null
+++ b/drivers/gpu/drm/drm_sysfb.c
@@ -0,0 +1,145 @@
+/*
+ * DRM Bus for sysfb drivers
+ *
+ * Copyright 2013 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/sysfb.h>
+#include <drm/drmP.h>
+
+int drm_get_sysfb_dev(struct sysfb_device *sdev,
+ struct drm_driver *driver)
+{
+ struct drm_device *dev;
+ int ret;
+
+ DRM_DEBUG("\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev_set_drvdata(&sdev->dev, dev);
+ dev->sysfbdev = sdev;
+ dev->dev = &sdev->dev;
+
+ mutex_lock(&drm_global_mutex);
+
+ ret = drm_fill_in_dev(dev, NULL, driver);
+ if (ret) {
+ DRM_ERROR("drm_fill_in_dev failed\n");
+ goto err_unlock;
+ }
+
+ if (drm_core_check_feature(dev, DRIVER_MODESET)) {
+ ret = drm_get_minor(dev, &dev->control, DRM_MINOR_CONTROL);
+ if (ret)
+ goto err_unlock;
+ }
+
+ ret = drm_get_minor(dev, &dev->primary, DRM_MINOR_LEGACY);
+ if (ret)
+ goto err_ctrl;
+
+ if (dev->driver->load) {
+ ret = dev->driver->load(dev, 0);
+ if (ret)
+ goto err_primary;
+ }
+
+ if (drm_core_check_feature(dev, DRIVER_MODESET)) {
+ ret = drm_mode_group_init_legacy_group(dev,
+ &dev->primary->mode_group);
+ if (ret)
+ goto err_primary;
+ }
+
+ list_add_tail(&dev->driver_item, &driver->device_list);
+
+ mutex_unlock(&drm_global_mutex);
+
+ DRM_INFO("initialized %s %d.%d.%d %s on minor %d\n",
+ driver->name, driver->major, driver->minor, driver->patchlevel,
+ driver->date, dev->primary->index);
+
+ return 0;
+
+err_primary:
+ drm_put_minor(&dev->primary);
+err_ctrl:
+ if (drm_core_check_feature(dev, DRIVER_MODESET))
+ drm_put_minor(&dev->control);
+err_unlock:
+ mutex_unlock(&drm_global_mutex);
+ kfree(dev);
+ return ret;
+}
+EXPORT_SYMBOL(drm_get_sysfb_dev);
+
+static int drm_sysfb_get_irq(struct drm_device *dev)
+{
+ return 0;
+}
+
+static const char *drm_sysfb_get_name(struct drm_device *dev)
+{
+ return dev_name(&dev->sysfbdev->dev);
+}
+
+static int drm_sysfb_set_busid(struct drm_device *dev,
+ struct drm_master *master)
+{
+ return 0;
+}
+
+static struct drm_bus drm_sysfb_bus = {
+ .bus_type = DRIVER_BUS_SYSFB,
+ .get_irq = drm_sysfb_get_irq,
+ .get_name = drm_sysfb_get_name,
+ .set_busid = drm_sysfb_set_busid,
+};
+
+int drm_sysfb_init(struct drm_driver *driver, struct sysfb_driver *sdrv)
+{
+ DRM_DEBUG("\n");
+
+ INIT_LIST_HEAD(&driver->device_list);
+ driver->kdriver.sysfb = sdrv;
+ driver->bus = &drm_sysfb_bus;
+
+ return sysfb_register_driver(sdrv);
+}
+EXPORT_SYMBOL(drm_sysfb_init);
+
+void drm_sysfb_exit(struct drm_driver *driver, struct sysfb_driver *sdrv)
+{
+ DRM_DEBUG("\n");
+
+ sysfb_unregister_driver(sdrv);
+ DRM_INFO("module unloaded\n");
+}
+EXPORT_SYMBOL(drm_sysfb_exit);
+
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("DRM sysfb support");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index 3fd8280..e5d73fe 100644
--- a/include/drm/drmP.h
+++ b/include/drm/drmP.h
@@ -71,6 +71,7 @@
#endif
#include <linux/workqueue.h>
#include <linux/poll.h>
+#include <linux/sysfb.h>
#include <asm/pgalloc.h>
#include <drm/drm.h>
#include <drm/drm_sarea.h>
@@ -157,6 +158,7 @@ int drm_err(const char *func, const char *format, ...);
#define DRIVER_BUS_PCI 0x1
#define DRIVER_BUS_PLATFORM 0x2
#define DRIVER_BUS_USB 0x3
+#define DRIVER_BUS_SYSFB 0x4
/***********************************************************************/
/** \name Begin the DRM... */
@@ -953,6 +955,7 @@ struct drm_driver {
struct pci_driver *pci;
struct platform_device *platform_device;
struct usb_driver *usb;
+ struct sysfb_driver *sysfb;
} kdriver;
struct drm_bus *bus;
@@ -1173,6 +1176,7 @@ struct drm_device {
struct platform_device *platformdev; /**< Platform device struture */
struct usb_device *usbdev;
+ struct sysfb_device *sysfbdev;
struct drm_sg_mem *sg; /**< Scatter gather memory */
unsigned int num_crtcs; /**< Number of CRTCs on this device */
diff --git a/include/drm/drm_sysfb.h b/include/drm/drm_sysfb.h
new file mode 100644
index 0000000..ced54d7
--- /dev/null
+++ b/include/drm/drm_sysfb.h
@@ -0,0 +1,35 @@
+#ifndef __DRM_SYSFB_H
+#define __DRM_SYSFB_H
+/*
+ * DRM Bus for sysfb drivers
+ *
+ * Copyright 2013 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <drmP.h>
+#include <linux/sysfb.h>
+
+int drm_sysfb_init(struct drm_driver *driver, struct sysfb_driver *sdrv);
+void drm_sysfb_exit(struct drm_driver *driver, struct sysfb_driver *sdrv);
+int drm_get_sysfb_dev(struct sysfb_device *sdev,
+ struct drm_driver *driver);
+
+#endif /* __DRM_SYSFB_H */
--
1.8.1.3
^ permalink raw reply related
* [PATCH 5/9] video: vesafb: use sysfb bus
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
Instead of using our own platform device, we now register as sysfb driver
and get notified whenever we are loaded on a system VBE device. This
allows other VBE drivers to be loaded at the same time and users can
bind/unbind drivers via sysfs. We also no longer need to fake a
platform-device because the sysfb bus provides all devices now.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
drivers/video/Kconfig | 1 +
drivers/video/vesafb.c | 49 +++++++++++++++----------------------------------
2 files changed, 16 insertions(+), 34 deletions(-)
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index d5723c2..5c23d32 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -776,6 +776,7 @@ config FB_VESA
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
select FB_BOOT_VESA_SUPPORT
+ select SYSFB
help
This is the frame buffer device driver for generic VESA 2.0
compliant graphic cards. The older VESA 1.2 cards are not supported.
diff --git a/drivers/video/vesafb.c b/drivers/video/vesafb.c
index 4ad7b40..652858f 100644
--- a/drivers/video/vesafb.c
+++ b/drivers/video/vesafb.c
@@ -19,6 +19,7 @@
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/screen_info.h>
+#include <linux/sysfb.h>
#include <video/vga.h>
#include <asm/io.h>
@@ -214,7 +215,7 @@ static int vesafb_setup(char *options)
return 0;
}
-static int vesafb_probe(struct platform_device *dev)
+static int vesafb_probe(struct sysfb_device *dev)
{
struct fb_info *info;
struct vesafb_par *par;
@@ -491,7 +492,7 @@ static int vesafb_probe(struct platform_device *dev)
}
printk(KERN_INFO "fb%d: %s frame buffer device\n",
info->node, info->fix.id);
- platform_set_drvdata(dev, info);
+ dev_set_drvdata(&dev->dev, info);
return 0;
err:
if (info->screen_base)
@@ -501,59 +502,39 @@ err:
return err;
}
-static int vesafb_remove(struct platform_device *dev)
+static void vesafb_remove(struct sysfb_device *dev)
{
- struct fb_info *info = platform_get_drvdata(dev);
+ struct fb_info *info = dev_get_drvdata(&dev->dev);
unregister_framebuffer(info);
- return 0;
}
-static struct platform_driver vesafb_driver = {
+static struct sysfb_driver vesafb_driver = {
+ .type_mask = SYSFB_VBE,
+ .allow_tainted = false,
+ .driver = {
+ .name = "vesafb",
+ .owner = THIS_MODULE,
+ .mod_name = KBUILD_MODNAME,
+ },
.probe = vesafb_probe,
.remove = vesafb_remove,
- .driver = {
- .name = "vesafb",
- },
};
-static struct platform_device *vesafb_device;
-
static int __init vesafb_init(void)
{
- int ret;
char *option = NULL;
/* ignore error return of fb_get_options */
fb_get_options("vesafb", &option);
vesafb_setup(option);
- vesafb_device = platform_device_alloc("vesafb", -1);
- if (!vesafb_device)
- return -ENOMEM;
-
- ret = platform_driver_register(&vesafb_driver);
- if (ret)
- goto err_dev;
-
- ret = platform_device_add(vesafb_device);
- if (ret)
- goto err_drv;
-
- return 0;
-
-err_drv:
- platform_driver_unregister(&vesafb_driver);
-err_dev:
- platform_device_put(vesafb_device);
- return ret;
+ return sysfb_register_driver(&vesafb_driver);
}
static void __exit vesafb_exit(void)
{
- platform_device_del(vesafb_device);
- platform_device_put(vesafb_device);
- platform_driver_unregister(&vesafb_driver);
+ sysfb_unregister_driver(&vesafb_driver);
}
module_init(vesafb_init);
--
1.8.1.3
^ permalink raw reply related
* [PATCH 4/9] video: vesafb: allow building as module
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
From: David Herrmann <dh.herrmann@googlemail.com>
Fix the vesafb module to no longer use any static __init data. Also add a
module_exit() function that destroys the platform device.
Note that fbdev hotplugging is broken and the self-reference actually
prevents sane module-unloading. Anyway, this at least allows delayed
module loading of vesafb and helps debugging vesafb a lot.
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
---
drivers/video/Kconfig | 2 +-
drivers/video/vesafb.c | 100 +++++++++++++++++++++++++++++++------------------
2 files changed, 64 insertions(+), 38 deletions(-)
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index eac56ef..d5723c2 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -770,7 +770,7 @@ config FB_UVESA
If unsure, say N.
config FB_VESA
- bool "VESA VGA graphics support"
+ tristate "VESA VGA graphics support"
depends on (FB = y) && X86
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
diff --git a/drivers/video/vesafb.c b/drivers/video/vesafb.c
index 501b340..4ad7b40 100644
--- a/drivers/video/vesafb.c
+++ b/drivers/video/vesafb.c
@@ -29,27 +29,10 @@
/* --------------------------------------------------------------------- */
-static struct fb_var_screeninfo vesafb_defined __initdata = {
- .activate = FB_ACTIVATE_NOW,
- .height = -1,
- .width = -1,
- .right_margin = 32,
- .upper_margin = 16,
- .lower_margin = 4,
- .vsync_len = 4,
- .vmode = FB_VMODE_NONINTERLACED,
-};
-
-static struct fb_fix_screeninfo vesafb_fix __initdata = {
- .id = "VESA VGA",
- .type = FB_TYPE_PACKED_PIXELS,
- .accel = FB_ACCEL_NONE,
-};
-
static int inverse __read_mostly;
static int mtrr __read_mostly; /* disable mtrr */
-static int vram_remap __initdata; /* Set amount of memory to be used */
-static int vram_total __initdata; /* Set total amount of memory */
+static int vram_remap; /* Set amount of memory to be used */
+static int vram_total; /* Set total amount of memory */
static int pmi_setpal __read_mostly = 1; /* pmi for palette changes ??? */
static int ypan __read_mostly; /* 0..nothing, 1..ypan, 2..ywrap */
static void (*pmi_start)(void) __read_mostly;
@@ -58,6 +41,11 @@ static int depth __read_mostly;
static int vga_compat __read_mostly;
/* --------------------------------------------------------------------- */
+struct vesafb_par {
+ struct fb_ops ops;
+ u32 palette[256];
+};
+
static int vesafb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
@@ -182,7 +170,7 @@ static void vesafb_destroy(struct fb_info *info)
framebuffer_release(info);
}
-static struct fb_ops vesafb_ops = {
+static const struct fb_ops vesafb_ops = {
.owner = THIS_MODULE,
.fb_destroy = vesafb_destroy,
.fb_setcolreg = vesafb_setcolreg,
@@ -192,7 +180,7 @@ static struct fb_ops vesafb_ops = {
.fb_imageblit = cfb_imageblit,
};
-static int __init vesafb_setup(char *options)
+static int vesafb_setup(char *options)
{
char *this_opt;
@@ -226,13 +214,29 @@ static int __init vesafb_setup(char *options)
return 0;
}
-static int __init vesafb_probe(struct platform_device *dev)
+static int vesafb_probe(struct platform_device *dev)
{
struct fb_info *info;
+ struct vesafb_par *par;
int i, err;
unsigned int size_vmode;
unsigned int size_remap;
unsigned int size_total;
+ struct fb_var_screeninfo vesafb_defined = {
+ .activate = FB_ACTIVATE_NOW,
+ .height = -1,
+ .width = -1,
+ .right_margin = 32,
+ .upper_margin = 16,
+ .lower_margin = 4,
+ .vsync_len = 4,
+ .vmode = FB_VMODE_NONINTERLACED,
+ };
+ struct fb_fix_screeninfo vesafb_fix = {
+ .id = "VESA VGA",
+ .type = FB_TYPE_PACKED_PIXELS,
+ .accel = FB_ACCEL_NONE,
+ };
if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB)
return -ENODEV;
@@ -287,13 +291,14 @@ static int __init vesafb_probe(struct platform_device *dev)
spaces our resource handlers simply don't know about */
}
- info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev);
+ info = framebuffer_alloc(sizeof(struct vesafb_par), &dev->dev);
if (!info) {
release_mem_region(vesafb_fix.smem_start, size_total);
return -ENOMEM;
}
- info->pseudo_palette = info->par;
- info->par = NULL;
+ par = info->par;
+ par->ops = vesafb_ops;
+ info->pseudo_palette = par->palette;
/* set vesafb aperture size for generic probing */
info->apertures = alloc_apertures(1);
@@ -466,7 +471,7 @@ static int __init vesafb_probe(struct platform_device *dev)
vesafb_fix.smem_start, info->screen_base,
size_remap/1024, size_total/1024);
- info->fbops = &vesafb_ops;
+ info->fbops = &par->ops;
info->var = vesafb_defined;
info->fix = vesafb_fix;
info->flags = FBINFO_FLAG_DEFAULT | FBINFO_MISC_FIRMWARE |
@@ -486,6 +491,7 @@ static int __init vesafb_probe(struct platform_device *dev)
}
printk(KERN_INFO "fb%d: %s frame buffer device\n",
info->node, info->fix.id);
+ platform_set_drvdata(dev, info);
return 0;
err:
if (info->screen_base)
@@ -495,7 +501,17 @@ err:
return err;
}
+static int vesafb_remove(struct platform_device *dev)
+{
+ struct fb_info *info = platform_get_drvdata(dev);
+
+ unregister_framebuffer(info);
+ return 0;
+}
+
static struct platform_driver vesafb_driver = {
+ .probe = vesafb_probe,
+ .remove = vesafb_remove,
.driver = {
.name = "vesafb",
},
@@ -512,24 +528,34 @@ static int __init vesafb_init(void)
fb_get_options("vesafb", &option);
vesafb_setup(option);
- vesafb_device = platform_device_alloc("vesafb", 0);
+ vesafb_device = platform_device_alloc("vesafb", -1);
if (!vesafb_device)
return -ENOMEM;
+ ret = platform_driver_register(&vesafb_driver);
+ if (ret)
+ goto err_dev;
+
ret = platform_device_add(vesafb_device);
- if (!ret) {
- ret = platform_driver_probe(&vesafb_driver, vesafb_probe);
- if (ret)
- platform_device_del(vesafb_device);
- }
+ if (ret)
+ goto err_drv;
- if (ret) {
- platform_device_put(vesafb_device);
- vesafb_device = NULL;
- }
+ return 0;
+err_drv:
+ platform_driver_unregister(&vesafb_driver);
+err_dev:
+ platform_device_put(vesafb_device);
return ret;
}
-module_init(vesafb_init);
+static void __exit vesafb_exit(void)
+{
+ platform_device_del(vesafb_device);
+ platform_device_put(vesafb_device);
+ platform_driver_unregister(&vesafb_driver);
+}
+
+module_init(vesafb_init);
+module_exit(vesafb_exit);
MODULE_LICENSE("GPL");
--
1.8.1.3
^ permalink raw reply related
* [PATCH 3/9] video: sysfb: always provide vbefb device
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
From: David Herrmann <dh.herrmann@googlemail.com>
HACK: This should be provided by architecture setup code. But to show how it is
supposed to work, we now simply add a "vbefb" device during initialization.
The better way to do this is by moving this into arch-code. So for instance the
x86 boot initialization should create this platform-device after VBE/VESA screen
detection. Other architectures can do the same or introduce other framebuffer
types than SYSFB_VBE.
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
---
drivers/video/sysfb.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/video/sysfb.c b/drivers/video/sysfb.c
index 5b47a9a..0c0a4e7 100644
--- a/drivers/video/sysfb.c
+++ b/drivers/video/sysfb.c
@@ -271,6 +271,8 @@ static struct platform_driver sysfb_vbe_driver = {
.remove = sysfb_vbe_remove,
};
+static struct platform_device *sysfb_vbe_device;
+
static int __init sysfb_init(void)
{
int ret;
@@ -287,6 +289,12 @@ static int __init sysfb_init(void)
goto err_bus;
}
+ sysfb_vbe_device = platform_device_register_data(NULL, "vbefb", -1,
+ &screen_info,
+ sizeof(screen_info));
+ if (!sysfb_vbe_device)
+ pr_warn("cannot create vbefb device\n");
+
return 0;
err_bus:
--
1.8.1.3
^ permalink raw reply related
* [PATCH 2/9] video: sysfb: new vbefb device type
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
From: David Herrmann <dh.herrmann@googlemail.com>
This adds the VESA BIOS Extension (VBE) device type. Platform code needs
to provide the "vbefb" platform-device with a screen_info structure as
platform code.
All drivers that depend on VBE can now register as bus drivers and bind
to SYSFB_VBE devices. There is no distinction between graphics
framebuffers or plain text VGA. Drivers ought to inspect the screen_info
and return -ENODEV during probe() is they cannot make use of the device.
Only one framebuffer of type SYSFB_VBE is available on a system at a time.
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
---
drivers/video/sysfb.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/sysfb.h | 5 +++-
2 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/drivers/video/sysfb.c b/drivers/video/sysfb.c
index 8249006..5b47a9a 100644
--- a/drivers/video/sysfb.c
+++ b/drivers/video/sysfb.c
@@ -205,6 +205,72 @@ void sysfb_taint(struct sysfb_device *sdev, bool set)
}
EXPORT_SYMBOL(sysfb_taint);
+static void sysfb_dev_release(struct device *dev)
+{
+ struct sysfb_device *sdev = to_sysfb_device(dev);
+
+ kfree(sdev);
+}
+
+static struct sysfb_device *sysfb_dev_new(struct device *parent)
+{
+ struct sysfb_device *sdev;
+
+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return NULL;
+
+ device_initialize(&sdev->dev);
+ sdev->dev.release = sysfb_dev_release;
+ sdev->dev.bus = &sysfb_bus_type;
+ sdev->dev.parent = parent;
+
+ return sdev;
+}
+
+static int sysfb_vbe_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct sysfb_device *sdev;
+
+ sdev = sysfb_dev_new(&pdev->dev);
+ if (!sdev)
+ return -ENOMEM;
+
+ dev_set_name(&sdev->dev, "vbefb");
+ sdev->type = SYSFB_VBE;
+ sdev->screen = pdev->dev.platform_data;
+
+ ret = device_add(&sdev->dev);
+ if (ret)
+ goto err_free;
+
+ platform_set_drvdata(pdev, sdev);
+ return 0;
+
+err_free:
+ put_device(&sdev->dev);
+ return ret;
+}
+
+static int sysfb_vbe_remove(struct platform_device *pdev)
+{
+ struct sysfb_device *sdev = platform_get_drvdata(pdev);
+
+ device_del(&sdev->dev);
+ put_device(&sdev->dev);
+ return 0;
+}
+
+static struct platform_driver sysfb_vbe_driver = {
+ .driver = {
+ .name = "vbefb",
+ .owner = THIS_MODULE,
+ },
+ .probe = sysfb_vbe_probe,
+ .remove = sysfb_vbe_remove,
+};
+
static int __init sysfb_init(void)
{
int ret;
@@ -215,11 +281,22 @@ static int __init sysfb_init(void)
return ret;
}
+ ret = platform_driver_register(&sysfb_vbe_driver);
+ if (ret) {
+ pr_err("cannot register VBE framebuffer driver\n");
+ goto err_bus;
+ }
+
return 0;
+
+err_bus:
+ bus_unregister(&sysfb_bus_type);
+ return ret;
}
static void __exit sysfb_exit(void)
{
+ platform_driver_unregister(&sysfb_vbe_driver);
bus_unregister(&sysfb_bus_type);
}
diff --git a/include/linux/sysfb.h b/include/linux/sysfb.h
index 6cd3c24..1796c1e 100644
--- a/include/linux/sysfb.h
+++ b/include/linux/sysfb.h
@@ -20,6 +20,7 @@
/**
* sysfb_type
+ * @SYSFB_VBE: VESA BIOS Extension compatible device (includes VGA devices)
*
* Different types of available framebuffer devices. Only one device of each
* type can be available at a time. In most systems there even is only one
@@ -29,7 +30,9 @@
* devices.
*/
enum sysfb_type {
- SYSFB_TYPES = 0,
+ SYSFB_VBE = 0x01,
+
+ SYSFB_TYPES = SYSFB_VBE,
};
/**
--
1.8.1.3
^ permalink raw reply related
* [PATCH 1/9] video: introduce system framebuffer bus
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
In-Reply-To: <1361123951-587-1-git-send-email-dh.herrmann@gmail.com>
From: David Herrmann <dh.herrmann@googlemail.com>
For a long time now we have the problem that there are multiple drivers
available that try to use system framebuffers (like EFI, VESA/VBE, ...).
There is no way to control which driver gets access to the devices, but
instead works on a first-come-first-serve basis.
Furthermore, hardware drivers (eg., gpu/drm/*) that get loaded on the
real hardware bus (eg., pci-bus) of the framebuffer devices have a hard
time unloading other drivers that currently use system framebuffers.
This introduces a new bus-type: sysfb (system framebuffer bus)
Any available system framebuffer gets registered as a device on this bus.
A bus-driver can then pick up the device and use it. Standard sysfs
bind/unbind interfaces can be used to change drivers on-the-fly.
There are actually two types of drivers: generic and real drivers
Generic drivers use the generic framebuffer interface exclusively. They
are often used as a fallback interface where no real driver for the
hardware is available. Generic drivers register as sysfb drivers to the
sysfb bus and will get loaded dynamically. User-space can bind/unbind them
via sysfs to control which driver should get access.
Only one driver can be active per device. During probe the driver can
retrieve additional information via a screen_info object of the device.
Generic drivers include: efifb, (u)vesafb, vgacon, ...
Real drivers work differently. Instead of being loaded via sysfb, they
register as drivers on the real bus (eg., pci-bus). During probe they
should verify whether their real device provides a system-framebuffer. If
it does, they call sysfb_claim() to claim exclusive access to the device.
This guarantees that any generic driver gets unloaded and the real
hardware driver can gain access. This also guarantees that a real hardware
driver always takes precedence over generic fallback drivers.
Real drivers include: i915, radeon, nouveau, ...
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
---
drivers/video/Kconfig | 17 ++++
drivers/video/Makefile | 1 +
drivers/video/sysfb.c | 230 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/sysfb.h | 134 ++++++++++++++++++++++++++++
4 files changed, 382 insertions(+)
create mode 100644 drivers/video/sysfb.c
create mode 100644 include/linux/sysfb.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index d08d799..eac56ef 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -27,6 +27,23 @@ config VGASTATE
tristate
default n
+config SYSFB
+ tristate "System Framebuffer Bus"
+ help
+ Framebuffers like VGA, VESA/VBE, EFI and others can be handled by many
+ different drivers. This bus provides an infrastructure for drivers to
+ register themselves and then get bound/unbound to these system-wide
+ framebuffers.
+ This bus prevents framebuffers from being used by multiple drivers
+ simultaneously and also provides a sysfs API to bind/rebind different
+ drivers to each device from userspace.
+
+ Chipset-specific drivers (like real GPU drivers) will always take
+ precedence over generic framebuffer drivers.
+
+ A driver should normally select this bus-option automatically. Enable
+ it only if you need out-of-tree builds.
+
config VIDEO_OUTPUT_CONTROL
tristate "Lowlevel video output switch controls"
help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 23e948e..f0eb006 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -5,6 +5,7 @@
# Each configuration option enables a list of files.
obj-$(CONFIG_VGASTATE) += vgastate.o
+obj-$(CONFIG_SYSFB) += sysfb.o
obj-y += fb_notify.o
obj-$(CONFIG_FB) += fb.o
fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \
diff --git a/drivers/video/sysfb.c b/drivers/video/sysfb.c
new file mode 100644
index 0000000..8249006
--- /dev/null
+++ b/drivers/video/sysfb.c
@@ -0,0 +1,230 @@
+/*
+ * System framebuffer bus
+ * Copyright (c) 2013 David Herrmann
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * The system framebuffer bus (sysfb) provides a way to register global system
+ * framebuffers and load different drivers for it. This includes VESA/VBE and
+ * EFI framebuffers.
+ * Platform code is responsible of adding the framebuffer devices to the system
+ * platform bus. The sysfb bus will pick up known devices and provide them via
+ * the sysfb bus to system drivers. This guarantees that only one driver uses
+ * a single system framebuffer at a time.
+ *
+ * Drivers that can make use of the generic interfaces of system framebuffers
+ * should register as a sysfb driver. They will get notified via probe/remove
+ * callbacks just like any other hotpluggable driver. Users can load/unload
+ * drivers via the sysfs bus interface so drivers must be hotplug capable.
+ *
+ * Drivers that cannot make use of the generic interfaces but instead control
+ * the real hardware should instead claim the device. These drivers normally
+ * register through PCI or platform devices and control the device via another
+ * interface.
+ * By claiming a device, all other generic drivers are unregistered and no more
+ * drivers will be probed unless the device is released again.
+ *
+ * Only _real_ hardware drivers should claim devices as there is always another
+ * mechanism to control which real hardware driver gets loaded (eg. pci-bus).
+ * Generic drivers which aren't controlled via another bus should use this
+ * generic sysfb driver interface instead of claiming a device.
+ *
+ * All drivers must make sure that after they get unloaded or release a device,
+ * the device is reset to a usable state. If the driver cannot guarantee that,
+ * it should taint the device so other drivers will notice it and can
+ * optionally recover the device.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/screen_info.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/sysfb.h>
+
+static DEFINE_SPINLOCK(sysfb_lock);
+static unsigned int claimed_types;
+
+static int sysfb_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct sysfb_device *sdev = to_sysfb_device(dev);
+ struct sysfb_driver *sdrv = to_sysfb_driver(drv);
+
+ return (sdrv->type_mask & sdev->type) &&
+ (sdrv->allow_tainted || !sdev->tainted);
+}
+
+static int sysfb_bus_probe(struct device *dev)
+{
+ struct sysfb_device *sdev = to_sysfb_device(dev);
+ struct sysfb_driver *sdrv = to_sysfb_driver(dev->driver);
+ unsigned long flags;
+ int ret;
+
+ if (!(sdrv->type_mask & sdev->type))
+ return -ENODEV;
+ if (!sdrv->allow_tainted && sdev->tainted)
+ return -ENODEV;
+
+ spin_lock_irqsave(&sysfb_lock, flags);
+ if ((claimed_types & sdev->type)) {
+ spin_unlock_irqrestore(&sysfb_lock, flags);
+ return -ENODEV;
+ }
+ spin_unlock_irqrestore(&sysfb_lock, flags);
+
+ if (sdrv->probe) {
+ ret = sdrv->probe(sdev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sysfb_bus_remove(struct device *dev)
+{
+ struct sysfb_device *sdev = to_sysfb_device(dev);
+ struct sysfb_driver *sdrv = to_sysfb_driver(dev->driver);
+
+ if (sdrv->remove)
+ sdrv->remove(sdev);
+
+ return 0;
+}
+
+static struct bus_type sysfb_bus_type = {
+ .name = "sysfb",
+ .match = sysfb_bus_match,
+ .probe = sysfb_bus_probe,
+ .remove = sysfb_bus_remove,
+};
+
+int sysfb_register_driver(struct sysfb_driver *drv)
+{
+ int ret;
+
+ drv->driver.bus = &sysfb_bus_type;
+
+ ret = driver_register(&drv->driver);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL(sysfb_register_driver);
+
+void sysfb_unregister_driver(struct sysfb_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL(sysfb_unregister_driver);
+
+static int __sysfb_rescan(struct device *dev, void *data)
+{
+ return device_attach(dev);
+}
+
+static void sysfb_rescan(void)
+{
+ bus_for_each_dev(&sysfb_bus_type, NULL, NULL, __sysfb_rescan);
+}
+
+static int __sysfb_claim(struct device *dev, void *data)
+{
+ struct sysfb_device *sdev = to_sysfb_device(dev);
+ unsigned int claim = (long)data;
+
+ if (!(sdev->type & claim))
+ return 0;
+
+ device_release_driver(dev);
+ return 0;
+}
+
+int sysfb_claim(unsigned int types)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!(types & SYSFB_TYPES))
+ return -EINVAL;
+
+ spin_lock_irqsave(&sysfb_lock, flags);
+ if ((claimed_types & types)) {
+ spin_unlock_irqrestore(&sysfb_lock, flags);
+ return -EALREADY;
+ }
+ claimed_types |= types;
+ spin_unlock_irqrestore(&sysfb_lock, flags);
+
+ ret = bus_for_each_dev(&sysfb_bus_type, NULL, (void*)(long)types,
+ __sysfb_claim);
+ if (ret)
+ goto err_restore;
+
+ return 0;
+
+err_restore:
+ spin_lock_irqsave(&sysfb_lock, flags);
+ claimed_types &= ~types;
+ spin_unlock_irqrestore(&sysfb_lock, flags);
+
+ sysfb_rescan();
+ return ret;
+}
+EXPORT_SYMBOL(sysfb_claim);
+
+void sysfb_release(unsigned int types)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sysfb_lock, flags);
+ claimed_types &= ~types;
+ spin_unlock_irqrestore(&sysfb_lock, flags);
+
+ sysfb_rescan();
+}
+EXPORT_SYMBOL(sysfb_release);
+
+void sysfb_taint(struct sysfb_device *sdev, bool set)
+{
+ sdev->tainted = set;
+}
+EXPORT_SYMBOL(sysfb_taint);
+
+static int __init sysfb_init(void)
+{
+ int ret;
+
+ ret = bus_register(&sysfb_bus_type);
+ if (ret) {
+ pr_err("cannot register sysfb bus\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit sysfb_exit(void)
+{
+ bus_unregister(&sysfb_bus_type);
+}
+
+module_init(sysfb_init);
+module_exit(sysfb_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("System framebuffer bus");
diff --git a/include/linux/sysfb.h b/include/linux/sysfb.h
new file mode 100644
index 0000000..6cd3c24
--- /dev/null
+++ b/include/linux/sysfb.h
@@ -0,0 +1,134 @@
+#ifndef __LINUX_SYSFB_H_
+#define __LINUX_SYSFB_H_
+
+/*
+ * System framebuffer bus
+ * Copyright (c) 2013 David Herrmann
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/screen_info.h>
+#include <linux/types.h>
+
+/**
+ * sysfb_type
+ *
+ * Different types of available framebuffer devices. Only one device of each
+ * type can be available at a time. In most systems there even is only one
+ * device at all.
+ *
+ * Use the sysfb_device->screen pointer to get information about the framebuffer
+ * devices.
+ */
+enum sysfb_type {
+ SYSFB_TYPES = 0,
+};
+
+/**
+ * sysfb_device
+ * @tainted: whether the device was tainted or not
+ * @type: type of the fb device (@sysfb_type)
+ * @screen: pointer to supplemental screen-info object
+ * @dev: device object
+ *
+ * Each framebuffer device is represented by a sysfb_device object. The sysfb
+ * core manages them and they cannot be registered from the outside.
+ */
+struct sysfb_device {
+ bool tainted;
+ unsigned int type;
+ struct screen_info *screen;
+ struct device dev;
+};
+
+#define to_sysfb_device(_dev) container_of((_dev), struct sysfb_device, dev)
+
+/**
+ * sysfb_driver
+ * @type_mask: mask of device-types that are supported (@sysfb_type)
+ * @allow_tainted: whether the driver can be bound to tainted devices
+ * @driver: driver object
+ * @probe: probe callback
+ * @remove: remove callback
+ *
+ * Each generic framebuffer driver must provide this structure when registering
+ * to the sysfb core. The @driver field must also be provided by the caller
+ * except for the 'driver.bus' field which is initialized by the core.
+ */
+struct sysfb_driver {
+ unsigned int type_mask;
+ bool allow_tainted;
+ struct device_driver driver;
+
+ int (*probe) (struct sysfb_device *dev);
+ void (*remove) (struct sysfb_device *dev);
+};
+
+#define to_sysfb_driver(_drv) container_of((_drv), struct sysfb_driver, driver)
+
+/**
+ * sysfb_register_driver
+ * @drv: Driver object
+ *
+ * Register a new driver on the sysfb bus.
+ */
+int sysfb_register_driver(struct sysfb_driver *drv);
+
+/**
+ * sysfb_unregister_driver
+ * @drv: Driver object
+ *
+ * Remove a driver from the sysfb bus.
+ */
+void sysfb_unregister_driver(struct sysfb_driver *drv);
+
+/**
+ * sysfb_claim
+ * @types: Bitmask of sysfb_type flags
+ *
+ * Unbind all drivers from all devices matching the given types and prevent
+ * further drivers to get loaded on these types of devices. This allows real
+ * hardware drivers that are loaded by other bus-types (eg. pci-bus) to prevent
+ * any generic driver from using the given framebuffer types.
+ *
+ * Return 0 if the types could be claimed, otherwise a negative error code
+ * is returned.
+ */
+int sysfb_claim(unsigned int types);
+
+/**
+ * sysfb_release
+ * @types: Bitmask of sysfb_type flags
+ *
+ * Releases the given previously claimed types. See sysfb_claim(). This does
+ * not check whether the types are actually claimed or who claimed them. So make
+ * sure to call this only when you really claimed these types previously.
+ */
+void sysfb_release(unsigned int types);
+
+/**
+ * sysfb_taint
+ * @sdev: sysfb device
+ * @set: whether to taint or untaint
+ *
+ * This taints a given sysfb device. This should be done by all drivers if they
+ * change the framebuffer device in a way that other generic drivers might not
+ * be able to detect afterwards.
+ * This includes changing the resolution or properties of a framebuffer without
+ * adjusting the screen_info object.
+ * This can be reset to 'false' after all the changes have been undone.
+ *
+ * This is an unlocked function. You must call it from within your probe/remove
+ * callbacks in the driver.
+ */
+void sysfb_taint(struct sysfb_device *sdev, bool set);
+
+#endif /* __LINUX_SYSFB_H_ */
--
1.8.1.3
^ permalink raw reply related
* [PATCH 0/9] System Framebuffer Bus (sysfb)
From: David Herrmann @ 2013-02-17 17:59 UTC (permalink / raw)
To: linux-kernel
Cc: Florian Tobias Schandinat, linux-fbdev, David Airlie, dri-devel,
David Herrmann
Hi
This series tries to fix the mess with global system framebuffer access in
device drivers. Currently, architecture initialization sets the "screen_info"
object according to the system framebuffer that was detected during boot. The
device driver that can map VMEM first gets access to it. There is no way to give
a specific driver access to the device and no _proper_ way to revoke access
again. In fact, some drivers don't even check whether they mapped the VMEM
successfully, letting multiple drivers to access the system framebuffer at the
same time.
This series introduces a new bus in patch #1. Global system framebuffers are
added as devices to this bus and drivers can register as bus drivers. Via sysfs
we can now bind/unbind the drivers. This guarantees that only one driver has
access to a single system-framebuffer at a time. But we can also easily control
which driver gets loaded (and extend this logic at a central place if we want).
If a real hardware drivers gets loaded via another bus that actually provides
the system-framebuffer (like pci-bus), these drivers can use a special BUS
function to claim the system framebuffer and unload all other drivers from this
device. This can be used by _real_ DRM drivers that want to kill all other
generic framebuffer drivers.
This series adds a SYSFB_VBE (VESA/VBE) framebuffer type as example, but the bus
can be used with any other system-framebuffer type. Any other architecture can
add their own type like SYSFB_VBE. Patch #3 is currently a HACK to provide the
VBE framebuffer on all architectures. However, the platform-device for
system-framebuffers should instead be provided by architecture code.
Why a new BUS type?
We need a way to allow transition from a generic driver to a real hardware
driver (like most DRM drivers or special fbdev drivers). We could implement this
logic separately, but the BUS driver-core code is available, anyway. So lets use
it and save a lot of .text space.
Additionally, we get some extra features like driver binding/unbinding via sysfs
for free (which is really handy for debugging).
The new bus is actually implemented in <200 lines of code. I don't think we can
get a smaller implementation when not using the bus-core code.
Patch #4 fixes vesafb.c to be hotplug-capable. It doesn't depend on this new bus
so it would be nice if it could get applied right away. It allows vesafb to be
compiled as a module.
Patch #5 makes vesafb.c register as new bus driver on the system-framebuffer
bus.
Patch #6-#9 introduce DVBE. It's a DRM driver based on VBE/VESA. It also uses
the new system-framebuffer bus and provides merely the same functionality as
vesafb.c but with a sane user-space API and without any fbdev dependency.
What needs to be done?
All drivers that use screen_info currently don't _have_ to be converted to the
new bus as the request_memory() calls protect the drivers from interfering. So
this new bus works even if no other driver gets converted. However, we really
_should_ convert the drivers. I will do that if a maintainer agrees to take the
bus code through their tree. But I hope to avoid converting all drivers if no
maintainer thinks this bus is a good idea.
The DVBE and vesafb drivers show how it is done.
I also like to see the system-framebuffer platform-devices being registered
during architecture initialization. I haven't worked much there so any comments
are welcome. Otherwise, I will keep the HACK to add the devices during sysfb
module-init.
Regards
David
David Herrmann (9):
video: introduce system framebuffer bus
video: sysfb: new vbefb device type
video: sysfb: always provide vbefb device
video: vesafb: allow building as module
video: vesafb: use sysfb bus
drm: new sysfb DRM bus module
drm: new VESA BIOS Extension DRM driver stub
drm: dvbe: implement VBE/VESA blitting backend
drm: dvbe: add optional fbdev frontend
drivers/gpu/drm/Kconfig | 7 +
drivers/gpu/drm/Makefile | 2 +
drivers/gpu/drm/drm_sysfb.c | 145 +++++++++++++
drivers/gpu/drm/dvbe/Kconfig | 47 +++++
drivers/gpu/drm/dvbe/Makefile | 5 +
drivers/gpu/drm/dvbe/dvbe.h | 121 +++++++++++
drivers/gpu/drm/dvbe/dvbe_drv.c | 104 +++++++++
drivers/gpu/drm/dvbe/dvbe_fbdev.c | 235 +++++++++++++++++++++
drivers/gpu/drm/dvbe/dvbe_main.c | 432 ++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/dvbe/dvbe_mem.c | 269 ++++++++++++++++++++++++
drivers/gpu/drm/dvbe/dvbe_vesa.c | 263 +++++++++++++++++++++++
drivers/video/Kconfig | 20 +-
drivers/video/Makefile | 1 +
drivers/video/sysfb.c | 315 +++++++++++++++++++++++++++
drivers/video/vesafb.c | 105 ++++-----
include/drm/drmP.h | 4 +
include/drm/drm_sysfb.h | 35 +++
include/linux/sysfb.h | 137 ++++++++++++
18 files changed, 2197 insertions(+), 50 deletions(-)
create mode 100644 drivers/gpu/drm/drm_sysfb.c
create mode 100644 drivers/gpu/drm/dvbe/Kconfig
create mode 100644 drivers/gpu/drm/dvbe/Makefile
create mode 100644 drivers/gpu/drm/dvbe/dvbe.h
create mode 100644 drivers/gpu/drm/dvbe/dvbe_drv.c
create mode 100644 drivers/gpu/drm/dvbe/dvbe_fbdev.c
create mode 100644 drivers/gpu/drm/dvbe/dvbe_main.c
create mode 100644 drivers/gpu/drm/dvbe/dvbe_mem.c
create mode 100644 drivers/gpu/drm/dvbe/dvbe_vesa.c
create mode 100644 drivers/video/sysfb.c
create mode 100644 include/drm/drm_sysfb.h
create mode 100644 include/linux/sysfb.h
--
1.8.1.3
^ permalink raw reply
* Re: [PATCH v2 1/1] OMAP4: DSS: Add panel for Blaze Tablet boards
From: Andi Shyti @ 2013-02-17 14:46 UTC (permalink / raw)
To: linux-fbdev
In-Reply-To: <1360338220-12753-2-git-send-email-ruslan.bilovol@ti.com>
Ops, sorry, I have to resend the e-mail since I used a mail
address which is not subscribed to the lkml.
Andi
On Sun, Feb 17, 2013 at 03:17:57PM +0100, Andi Shyti wrote:
> > >> + char name[30];
> > >> + char buf[50];
> > >> +
> > >> + if (size >= sizeof(buf))
> > >> + size = sizeof(buf);
> > >
> > > what's the point of this?
> >
> > This is a way to limit copied from userspace data by available buffer size,
> > widely used in current kernel sources. Are you implying there is some
> > better (more graceful) way?
>
> No indeed :)
> There is no other way, sorry for polluting the review :)
>
> > >> + if ((pins[2] & 1) || (pins[3] & 1)) {
> > >> + lanes |= (1 << 1);
> > >> + ret |= tc358765_write_register(dssdev, PPI_D0S_CLRSIPOCOUNT,
> > >> + board_data->clrsipo);
> > >> + }
> > >> + if ((pins[4] & 1) || (pins[5] & 1)) {
> > >> + lanes |= (1 << 2);
> > >> + ret |= tc358765_write_register(dssdev, PPI_D1S_CLRSIPOCOUNT,
> > >> + board_data->clrsipo);
> > >> + }
> > >> + if ((pins[6] & 1) || (pins[7] & 1)) {
> > >> + lanes |= (1 << 3);
> > >> + ret |= tc358765_write_register(dssdev, PPI_D2S_CLRSIPOCOUNT,
> > >> + board_data->clrsipo);
> > >> + }
> > >> + if ((pins[8] & 1) || (pins[9] & 1)) {
> > >> + lanes |= (1 << 4);
> > >> + ret |= tc358765_write_register(dssdev, PPI_D3S_CLRSIPOCOUNT,
> > >> + board_data->clrsipo);
> > >> + }
> > >
> > > Can't this be done in one single multiwrighting command since
> > > this registers are consecutive?
> > >
> > > You build once the array to write and you send it at once.
> >
> > In this particular case I disagree. Yes, it will be a little bit
> > faster, however:
> > 1) we write this for panel initialization only (so no impact in other cases)
> > 2) multiwriting of array will make code reading more difficult
> >
> > So I would like to leave it as-is
> > Same is for next your similar comment.
>
> If the hw is providing us some ways for simplifying the code I
> would use it. In this case we are talking about the i2c feature
> of multiwriting and multireading.
>
> Let's assume that we want to write on 8 different consecutive
> registers. In my opinion this aproach is quite "heavy":
>
> uX register;
>
> register = value1;
> i2c_write(REG1, register);
>
> register = value2;
> i2c_write(REG2, register);
>
> ...
>
> Usually what I do is this:
>
> uX register[8];
>
> for (i = 0; i < 8; i++)
> register |= valuei << i; (or register[i] = valuei or whatever)
>
> i2c_multi_write(REG, register, 8);
>
> of course this is a simplified example in pseudocode. I think
> it's more readable and we are making a better use of the i2c
> protocol.
>
> In your case you have some if statement that are making the multi
> writing more difficult, but still is not impossible.
>
> At the end it's still a matter of taste, so that you are free to
> choose whatever you prefer :)
>
> Andi
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
^ permalink raw reply
* Re: [PATCH RFC] video: Add Hyper-V Synthetic Video Frame Buffer Driver
From: Olaf Hering @ 2013-02-17 14:32 UTC (permalink / raw)
To: Haiyang Zhang
Cc: FlorianSchandinat, linux-fbdev, kys, jasowang, linux-kernel,
devel
In-Reply-To: <1360955396-14183-1-git-send-email-haiyangz@microsoft.com>
On Fri, Feb 15, Haiyang Zhang wrote:
> +config HYPERV_FB
This should probably be FB_HYPERB to follow the naming of all other
drivers.
Olaf
^ permalink raw reply
* Re: [PATCH v2 1/1] OMAP4: DSS: Add panel for Blaze Tablet boards
From: Andi Shyti @ 2013-02-17 14:17 UTC (permalink / raw)
To: Ruslan Bilovol
Cc: Andi Shyti, tomi.valkeinen, FlorianSchandinat, linux-fbdev,
linux-kernel, linux-omap
In-Reply-To: <CAB=otbSpspZ9Z_fOpBvE1mPtt+JwZydZrgcR2LdkD_4CrGa_eg@mail.gmail.com>
> >> + char name[30];
> >> + char buf[50];
> >> +
> >> + if (size >= sizeof(buf))
> >> + size = sizeof(buf);
> >
> > what's the point of this?
>
> This is a way to limit copied from userspace data by available buffer size,
> widely used in current kernel sources. Are you implying there is some
> better (more graceful) way?
No indeed :)
There is no other way, sorry for polluting the review :)
> >> + if ((pins[2] & 1) || (pins[3] & 1)) {
> >> + lanes |= (1 << 1);
> >> + ret |= tc358765_write_register(dssdev, PPI_D0S_CLRSIPOCOUNT,
> >> + board_data->clrsipo);
> >> + }
> >> + if ((pins[4] & 1) || (pins[5] & 1)) {
> >> + lanes |= (1 << 2);
> >> + ret |= tc358765_write_register(dssdev, PPI_D1S_CLRSIPOCOUNT,
> >> + board_data->clrsipo);
> >> + }
> >> + if ((pins[6] & 1) || (pins[7] & 1)) {
> >> + lanes |= (1 << 3);
> >> + ret |= tc358765_write_register(dssdev, PPI_D2S_CLRSIPOCOUNT,
> >> + board_data->clrsipo);
> >> + }
> >> + if ((pins[8] & 1) || (pins[9] & 1)) {
> >> + lanes |= (1 << 4);
> >> + ret |= tc358765_write_register(dssdev, PPI_D3S_CLRSIPOCOUNT,
> >> + board_data->clrsipo);
> >> + }
> >
> > Can't this be done in one single multiwrighting command since
> > this registers are consecutive?
> >
> > You build once the array to write and you send it at once.
>
> In this particular case I disagree. Yes, it will be a little bit
> faster, however:
> 1) we write this for panel initialization only (so no impact in other cases)
> 2) multiwriting of array will make code reading more difficult
>
> So I would like to leave it as-is
> Same is for next your similar comment.
If the hw is providing us some ways for simplifying the code I
would use it. In this case we are talking about the i2c feature
of multiwriting and multireading.
Let's assume that we want to write on 8 different consecutive
registers. In my opinion this aproach is quite "heavy":
uX register;
register = value1;
i2c_write(REG1, register);
register = value2;
i2c_write(REG2, register);
...
Usually what I do is this:
uX register[8];
for (i = 0; i < 8; i++)
register |= valuei << i; (or register[i] = valuei or whatever)
i2c_multi_write(REG, register, 8);
of course this is a simplified example in pseudocode. I think
it's more readable and we are making a better use of the i2c
protocol.
In your case you have some if statement that are making the multi
writing more difficult, but still is not impossible.
At the end it's still a matter of taste, so that you are free to
choose whatever you prefer :)
Andi
^ permalink raw reply
* [PATCH] OMAPDSS: tpo-td043 panel: fix data passing between SPI/DSS parts
From: Grazvydas Ignotas @ 2013-02-17 0:43 UTC (permalink / raw)
To: linux-fbdev; +Cc: linux-omap, Tomi Valkeinen, Archit Taneja, Grazvydas Ignotas
This driver uses omap_dss_device that it gets from a board file through
SPI platfrom_data pointer to pass data from SPI to DSS portion of the
driver by using dev_set_drvdata(). However this trick no longer works,
as DSS core no longer uses omap_dss_device from a board file to create
the real device, so use a global pointer to accomplish this instead,
like other SPI panel drivers do.
Signed-off-by: Grazvydas Ignotas <notasas@gmail.com>
---
.../video/omap2/displays/panel-tpo-td043mtea1.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/drivers/video/omap2/displays/panel-tpo-td043mtea1.c b/drivers/video/omap2/displays/panel-tpo-td043mtea1.c
index 6b66439..048c983 100644
--- a/drivers/video/omap2/displays/panel-tpo-td043mtea1.c
+++ b/drivers/video/omap2/displays/panel-tpo-td043mtea1.c
@@ -63,6 +63,9 @@ struct tpo_td043_device {
u32 power_on_resume:1;
};
+/* used to pass spi_device from SPI to DSS portion of the driver */
+static struct tpo_td043_device *g_tpo_td043;
+
static int tpo_td043_write(struct spi_device *spi, u8 addr, u8 data)
{
struct spi_message m;
@@ -403,7 +406,7 @@ static void tpo_td043_disable(struct omap_dss_device *dssdev)
static int tpo_td043_probe(struct omap_dss_device *dssdev)
{
- struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&dssdev->dev);
+ struct tpo_td043_device *tpo_td043 = g_tpo_td043;
int nreset_gpio = dssdev->reset_gpio;
int ret = 0;
@@ -440,6 +443,8 @@ static int tpo_td043_probe(struct omap_dss_device *dssdev)
if (ret)
dev_warn(&dssdev->dev, "failed to create sysfs files\n");
+ dev_set_drvdata(&dssdev->dev, tpo_td043);
+
return 0;
fail_gpio_req:
@@ -505,6 +510,9 @@ static int tpo_td043_spi_probe(struct spi_device *spi)
return -ENODEV;
}
+ if (g_tpo_td043 != NULL)
+ return -EBUSY;
+
spi->bits_per_word = 16;
spi->mode = SPI_MODE_0;
@@ -521,7 +529,7 @@ static int tpo_td043_spi_probe(struct spi_device *spi)
tpo_td043->spi = spi;
tpo_td043->nreset_gpio = dssdev->reset_gpio;
dev_set_drvdata(&spi->dev, tpo_td043);
- dev_set_drvdata(&dssdev->dev, tpo_td043);
+ g_tpo_td043 = tpo_td043;
omap_dss_register_driver(&tpo_td043_driver);
@@ -534,6 +542,7 @@ static int tpo_td043_spi_remove(struct spi_device *spi)
omap_dss_unregister_driver(&tpo_td043_driver);
kfree(tpo_td043);
+ g_tpo_td043 = NULL;
return 0;
}
--
1.7.9.5
^ permalink raw reply related
* Re: [PATCH/RFC] mfd: as3711: add OF support
From: Simon Horman @ 2013-02-16 6:13 UTC (permalink / raw)
To: Guennadi Liakhovetski
Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Samuel Ortiz,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ, Mark Brown,
Magnus Damm, linux-kernel-u79uwXL29TY76Z2rM5mHXA, Richard Purdie,
Andrew Morton, Liam Girdwood
In-Reply-To: <20130216022630.GA11628-/R6kz+dDXgpPR4JQBCEnsQ@public.gmane.org>
On Sat, Feb 16, 2013 at 11:26:30AM +0900, Simon Horman wrote:
> On Fri, Feb 15, 2013 at 11:07:16AM +0100, Guennadi Liakhovetski wrote:
> > Add device-tree bindings to the AS3711 regulator and backlight drivers.
>
> Hi,
>
> at this stage I do not expect this code to go through the renesas tree.
> However, in order to provide a basis for work on renesas SoCs I have added
> this patch to the topic/backlight topic branch in the reneas tree on
> kernel.org and merged it into topic/all+next.
>
> In other words, I am not picking this patch up to merge it or add it to
> linux-next, rather I am storing it for reference.
I have dropped this patch due to the of_device_is_available build error
reported by kbuild and reported in another sub-thread.
^ permalink raw reply
* Re: [PATCH/RFC] mfd: as3711: add OF support
From: Simon Horman @ 2013-02-16 5:54 UTC (permalink / raw)
To: Guennadi Liakhovetski
Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Samuel Ortiz,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ, Mark Brown,
Magnus Damm, linux-kernel-u79uwXL29TY76Z2rM5mHXA, Richard Purdie,
Andrew Morton, Liam Girdwood
In-Reply-To: <Pine.LNX.4.64.1302151101140.7446-0199iw4Nj15frtckUFj5Ag@public.gmane.org>
On Fri, Feb 15, 2013 at 11:07:16AM +0100, Guennadi Liakhovetski wrote:
> Add device-tree bindings to the AS3711 regulator and backlight drivers.
>
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
>
> As usual - comments to the new bindings are very welcome!
>
> Documentation/devicetree/bindings/mfd/as3711.txt | 73 +++++++++++++
> drivers/mfd/as3711.c | 30 +++++-
> drivers/regulator/as3711-regulator.c | 69 ++++++++++++-
> drivers/video/backlight/as3711_bl.c | 118 +++++++++++++++++++++-
> 4 files changed, 282 insertions(+), 8 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/mfd/as3711.txt
>
> diff --git a/Documentation/devicetree/bindings/mfd/as3711.txt b/Documentation/devicetree/bindings/mfd/as3711.txt
> new file mode 100644
> index 0000000..d98cf18
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/as3711.txt
> @@ -0,0 +1,73 @@
> +AS3711 is an I2C PMIC from Austria MicroSystems with multiple DCDC and LDO power
> +supplies, a battery charger and an RTC. So far only bindings for the two stepup
> +DCDC converters are defined. Other DCDC and LDO supplies are configured, using
> +standard regulator properties, they must belong to a sub-node, called
> +"regulators" and be called "sd1" to "sd4" and "ldo1" to "ldo8." Stepup converter
> +configuration should be placed in a subnode, called "backlight."
> +
> +Compulsory properties:
> +- compatible : must be "ams,as3711"
> +- reg : specifies the I2C address
> +
> +To use the SU1 converter as a backlight source the following two properties must
> +be provided:
> +- su1-dev : framebuffer phandle
> +- su1-max-uA : maximum current
> +
> +To use the SU2 converter as a backlight source the following two properties must
> +be provided:
> +- su2-dev : framebuffer phandle
> +- su1-max-uA : maximum current
> +
> +Additionally one of these properties must be provided to select the type of
> +feedback used:
> +- su2-feedback-voltage : voltage feedback is used
> +- su2-feedback-curr1 : CURR1 input used for current feedback
> +- su2-feedback-curr2 : CURR2 input used for current feedback
> +- su2-feedback-curr3 : CURR3 input used for current feedback
> +- su2-feedback-curr-auto: automatic current feedback selection
> +
> +and one of these to select the over-voltage protection pin
> +- su2-fbprot-lx-sd4 : LX_SD4 is used for over-voltage protection
> +- su2-fbprot-gpio2 : GPIO2 is used for over-voltage protection
> +- su2-fbprot-gpio3 : GPIO3 is used for over-voltage protection
> +- su2-fbprot-gpio4 : GPIO4 is used for over-voltage protection
> +
> +If "su2-feedback-curr-auto" is selected, one or more of the following properties
> +have to be specified:
> +- su2-auto-curr1 : use CURR1 input for current feedback
> +- su2-auto-curr2 : use CURR2 input for current feedback
> +- su2-auto-curr3 : use CURR3 input for current feedback
> +
> +Example:
> +
> +as3711@40 {
> + compatible = "ams,as3711";
> + reg = <0x40>;
> +
> + regulators {
> + sd4 {
> + regulator-name = "1.215V";
> + regulator-min-microvolt = <1215000>;
> + regulator-max-microvolt = <1235000>;
> + };
> + ldo2 {
> + regulator-name = "2.8V CPU";
> + regulator-min-microvolt = <2800000>;
> + regulator-max-microvolt = <2800000>;
> + regulator-always-on;
> + regulator-boot-on;
> + };
> + };
> +
> + backlight {
> + compatible = "ams,as3711-bl";
> + su2-dev = <&lcdc>;
> + su2-max-uA = <36000>;
> + su2-feedback-curr-auto;
> + su2-fbprot-gpio4;
> + su2-auto-curr1;
> + su2-auto-curr2;
> + su2-auto-curr3;
> + };
> +};
> diff --git a/drivers/mfd/as3711.c b/drivers/mfd/as3711.c
> index e994c96..5e0e8b3 100644
> --- a/drivers/mfd/as3711.c
> +++ b/drivers/mfd/as3711.c
> @@ -112,16 +112,37 @@ static const struct regmap_config as3711_regmap_config = {
> .cache_type = REGCACHE_RBTREE,
> };
>
> +#ifdef CONFIG_OF
> +static struct of_device_id as3711_of_match[] = {
> + {.compatible = "ams,as3711",},
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, as3711_of_match);
> +#endif
> +
> static int as3711_i2c_probe(struct i2c_client *client,
> const struct i2c_device_id *id)
> {
> struct as3711 *as3711;
> - struct as3711_platform_data *pdata = client->dev.platform_data;
> + struct as3711_platform_data *pdata;
> unsigned int id1, id2;
> int ret;
>
> - if (!pdata)
> - dev_dbg(&client->dev, "Platform data not found\n");
> + if (!client->dev.of_node) {
> + pdata = client->dev.platform_data;
> + if (!pdata)
> + dev_dbg(&client->dev, "Platform data not found\n");
> + } else {
> + if (!of_device_is_available(client->dev.of_node))
> + return -ENODEV;
The wonder that is kbuild tells me that the above call to
of_device_is_available() may cause a build-time error.
Perhaps it or this entire else clause should be guarded by CONFIG_OF?
> +
> + pdata = devm_kzalloc(&client->dev,
> + sizeof(*pdata), GFP_KERNEL);
> + if (!pdata) {
> + dev_err(&client->dev, "Failed to allocate pdata\n");
> + return -ENOMEM;
> + }
> + }
>
> as3711 = devm_kzalloc(&client->dev, sizeof(struct as3711), GFP_KERNEL);
> if (!as3711) {
> @@ -193,7 +214,8 @@ static struct i2c_driver as3711_i2c_driver = {
> .driver = {
> .name = "as3711",
> .owner = THIS_MODULE,
> - },
> + .of_match_table = of_match_ptr(as3711_of_match),
> + },
> .probe = as3711_i2c_probe,
> .remove = as3711_i2c_remove,
> .id_table = as3711_i2c_id,
> diff --git a/drivers/regulator/as3711-regulator.c b/drivers/regulator/as3711-regulator.c
> index f0ba8c4..cf145fc 100644
> --- a/drivers/regulator/as3711-regulator.c
> +++ b/drivers/regulator/as3711-regulator.c
> @@ -13,9 +13,11 @@
> #include <linux/init.h>
> #include <linux/mfd/as3711.h>
> #include <linux/module.h>
> +#include <linux/of.h>
> #include <linux/platform_device.h>
> #include <linux/regmap.h>
> #include <linux/regulator/driver.h>
> +#include <linux/regulator/of_regulator.h>
> #include <linux/slab.h>
>
> struct as3711_regulator_info {
> @@ -276,6 +278,57 @@ static struct as3711_regulator_info as3711_reg_info[] = {
>
> #define AS3711_REGULATOR_NUM ARRAY_SIZE(as3711_reg_info)
>
> +static const char *as3711_regulator_of_names[AS3711_REGULATOR_NUM] = {
> + [AS3711_REGULATOR_SD_1] = "sd1",
> + [AS3711_REGULATOR_SD_2] = "sd2",
> + [AS3711_REGULATOR_SD_3] = "sd3",
> + [AS3711_REGULATOR_SD_4] = "sd4",
> + [AS3711_REGULATOR_LDO_1] = "ldo1",
> + [AS3711_REGULATOR_LDO_2] = "ldo2",
> + [AS3711_REGULATOR_LDO_3] = "ldo3",
> + [AS3711_REGULATOR_LDO_4] = "ldo4",
> + [AS3711_REGULATOR_LDO_5] = "ldo5",
> + [AS3711_REGULATOR_LDO_6] = "ldo6",
> + [AS3711_REGULATOR_LDO_7] = "ldo7",
> + [AS3711_REGULATOR_LDO_8] = "ldo8",
> +};
> +
> +static int as3711_regulator_parse_dt(struct device *dev)
> +{
> + struct as3711_regulator_pdata *pdata = dev_get_platdata(dev);
> + struct device_node *regulators > + of_find_node_by_name(dev->parent->of_node, "regulators");
> + struct of_regulator_match *matches, *match;
> + const int count = AS3711_REGULATOR_NUM;
> + int ret, i;
> +
> + if (!regulators) {
> + dev_err(dev, "regulator node not found\n");
> + return -ENODEV;
> + }
> +
> + matches = devm_kzalloc(dev, sizeof(*matches) * count, GFP_KERNEL);
> + if (!matches)
> + return -ENOMEM;
> +
> + for (i = 0, match = matches; i < count; i++, match++) {
> + match->name = as3711_regulator_of_names[i];
> + match->driver_data = as3711_reg_info + i;
> + }
> +
> + ret = of_regulator_match(dev->parent, regulators, matches, count);
> + if (ret < 0) {
> + dev_err(dev, "Error parsing regulator init data: %d\n", ret);
> + return ret;
> + }
> +
> + for (i = 0, match = matches; i < count; i++, match++)
> + if (match->of_node)
> + pdata->init_data[i] = match->init_data;
> +
> + return 0;
> +}
> +
> static int as3711_regulator_probe(struct platform_device *pdev)
> {
> struct as3711_regulator_pdata *pdata = dev_get_platdata(&pdev->dev);
> @@ -289,8 +342,18 @@ static int as3711_regulator_probe(struct platform_device *pdev)
> int ret;
> int id;
>
> - if (!pdata)
> - dev_dbg(&pdev->dev, "No platform data...\n");
> + if (!pdata) {
> + dev_err(&pdev->dev, "No platform data...\n");
> + return -ENODEV;
> + }
> +
> + if (pdev->dev.parent->of_node) {
> + ret = as3711_regulator_parse_dt(&pdev->dev);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
> + return ret;
> + }
> + }
>
> regs = devm_kzalloc(&pdev->dev, AS3711_REGULATOR_NUM *
> sizeof(struct as3711_regulator), GFP_KERNEL);
> @@ -300,7 +363,7 @@ static int as3711_regulator_probe(struct platform_device *pdev)
> }
>
> for (id = 0, ri = as3711_reg_info; id < AS3711_REGULATOR_NUM; ++id, ri++) {
> - reg_data = pdata ? pdata->init_data[id] : NULL;
> + reg_data = pdata->init_data[id];
>
> /* No need to register if there is no regulator data */
> if (!reg_data)
> diff --git a/drivers/video/backlight/as3711_bl.c b/drivers/video/backlight/as3711_bl.c
> index c6bc65d..90c9208 100644
> --- a/drivers/video/backlight/as3711_bl.c
> +++ b/drivers/video/backlight/as3711_bl.c
> @@ -257,6 +257,109 @@ static int as3711_bl_register(struct platform_device *pdev,
> return 0;
> }
>
> +static int as3711_backlight_parse_dt(struct device *dev)
> +{
> + struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
> + struct device_node *bl > + of_find_node_by_name(dev->parent->of_node, "backlight"), *fb;
> + int ret;
> +
> + if (!bl) {
> + dev_dbg(dev, "backlight node not found\n");
> + return -ENODEV;
> + }
> +
> + fb = of_parse_phandle(bl, "su1-dev", 0);
> + if (fb) {
> + pdata->su1_fb = fb->full_name;
> +
> + ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
> + if (pdata->su1_max_uA <= 0)
> + ret = -EINVAL;
> + if (ret < 0)
> + return ret;
> + }
> +
> + fb = of_parse_phandle(bl, "su2-dev", 0);
> + if (fb) {
> + int count = 0;
> +
> + pdata->su2_fb = fb->full_name;
> +
> + ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA);
> + if (pdata->su2_max_uA <= 0)
> + ret = -EINVAL;
> + if (ret < 0)
> + return ret;
> +
> + if (of_find_property(bl, "su2-feedback-voltage", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_VOLTAGE;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr1", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR1;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr2", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR2;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr3", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR3;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR_AUTO;
> + count++;
> + }
> + if (count != 1)
> + return -EINVAL;
> +
> + count = 0;
> + if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_LX_SD4;
> + count++;
> + }
> + if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_GPIO2;
> + count++;
> + }
> + if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_GPIO3;
> + count++;
> + }
> + if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_GPIO4;
> + count++;
> + }
> + if (count != 1)
> + return -EINVAL;
> +
> + count = 0;
> + if (of_find_property(bl, "su2-auto-curr1", NULL)) {
> + pdata->su2_auto_curr1 = true;
> + count++;
> + }
> + if (of_find_property(bl, "su2-auto-curr2", NULL)) {
> + pdata->su2_auto_curr2 = true;
> + count++;
> + }
> + if (of_find_property(bl, "su2-auto-curr3", NULL)) {
> + pdata->su2_auto_curr3 = true;
> + count++;
> + }
> +
> + /*
> + * At least one su2-auto-curr* must be specified iff
> + * AS3711_SU2_CURR_AUTO is used
> + */
> + if (!count ^ pdata->su2_feedback != AS3711_SU2_CURR_AUTO)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> static int as3711_backlight_probe(struct platform_device *pdev)
> {
> struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
> @@ -266,11 +369,24 @@ static int as3711_backlight_probe(struct platform_device *pdev)
> unsigned int max_brightness;
> int ret;
>
> - if (!pdata || (!pdata->su1_fb && !pdata->su2_fb)) {
> + if (!pdata) {
> dev_err(&pdev->dev, "No platform data, exiting...\n");
> return -ENODEV;
> }
>
> + if (pdev->dev.parent->of_node) {
> + ret = as3711_backlight_parse_dt(&pdev->dev);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
> + return ret;
> + }
> + }
> +
> + if (!pdata->su1_fb && !pdata->su2_fb) {
> + dev_err(&pdev->dev, "No framebuffer specified\n");
> + return -EINVAL;
> + }
> +
> /*
> * Due to possible hardware damage I chose to block all modes,
> * unsupported on my hardware. Anyone, wishing to use any of those modes
> --
> 1.7.2.5
>
^ permalink raw reply
* Re: [PATCH/RFC] mfd: as3711: add OF support
From: Simon Horman @ 2013-02-16 2:26 UTC (permalink / raw)
To: Guennadi Liakhovetski
Cc: linux-kernel, Magnus Damm, devicetree-discuss, Samuel Ortiz,
Mark Brown, Liam Girdwood, Richard Purdie, Andrew Morton,
linux-fbdev
In-Reply-To: <Pine.LNX.4.64.1302151101140.7446@axis700.grange>
On Fri, Feb 15, 2013 at 11:07:16AM +0100, Guennadi Liakhovetski wrote:
> Add device-tree bindings to the AS3711 regulator and backlight drivers.
Hi,
at this stage I do not expect this code to go through the renesas tree.
However, in order to provide a basis for work on renesas SoCs I have added
this patch to the topic/backlight topic branch in the reneas tree on
kernel.org and merged it into topic/all+next.
In other words, I am not picking this patch up to merge it or add it to
linux-next, rather I am storing it for reference.
>
> Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
> ---
>
> As usual - comments to the new bindings are very welcome!
>
> Documentation/devicetree/bindings/mfd/as3711.txt | 73 +++++++++++++
> drivers/mfd/as3711.c | 30 +++++-
> drivers/regulator/as3711-regulator.c | 69 ++++++++++++-
> drivers/video/backlight/as3711_bl.c | 118 +++++++++++++++++++++-
> 4 files changed, 282 insertions(+), 8 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/mfd/as3711.txt
>
> diff --git a/Documentation/devicetree/bindings/mfd/as3711.txt b/Documentation/devicetree/bindings/mfd/as3711.txt
> new file mode 100644
> index 0000000..d98cf18
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/as3711.txt
> @@ -0,0 +1,73 @@
> +AS3711 is an I2C PMIC from Austria MicroSystems with multiple DCDC and LDO power
> +supplies, a battery charger and an RTC. So far only bindings for the two stepup
> +DCDC converters are defined. Other DCDC and LDO supplies are configured, using
> +standard regulator properties, they must belong to a sub-node, called
> +"regulators" and be called "sd1" to "sd4" and "ldo1" to "ldo8." Stepup converter
> +configuration should be placed in a subnode, called "backlight."
> +
> +Compulsory properties:
> +- compatible : must be "ams,as3711"
> +- reg : specifies the I2C address
> +
> +To use the SU1 converter as a backlight source the following two properties must
> +be provided:
> +- su1-dev : framebuffer phandle
> +- su1-max-uA : maximum current
> +
> +To use the SU2 converter as a backlight source the following two properties must
> +be provided:
> +- su2-dev : framebuffer phandle
> +- su1-max-uA : maximum current
> +
> +Additionally one of these properties must be provided to select the type of
> +feedback used:
> +- su2-feedback-voltage : voltage feedback is used
> +- su2-feedback-curr1 : CURR1 input used for current feedback
> +- su2-feedback-curr2 : CURR2 input used for current feedback
> +- su2-feedback-curr3 : CURR3 input used for current feedback
> +- su2-feedback-curr-auto: automatic current feedback selection
> +
> +and one of these to select the over-voltage protection pin
> +- su2-fbprot-lx-sd4 : LX_SD4 is used for over-voltage protection
> +- su2-fbprot-gpio2 : GPIO2 is used for over-voltage protection
> +- su2-fbprot-gpio3 : GPIO3 is used for over-voltage protection
> +- su2-fbprot-gpio4 : GPIO4 is used for over-voltage protection
> +
> +If "su2-feedback-curr-auto" is selected, one or more of the following properties
> +have to be specified:
> +- su2-auto-curr1 : use CURR1 input for current feedback
> +- su2-auto-curr2 : use CURR2 input for current feedback
> +- su2-auto-curr3 : use CURR3 input for current feedback
> +
> +Example:
> +
> +as3711@40 {
> + compatible = "ams,as3711";
> + reg = <0x40>;
> +
> + regulators {
> + sd4 {
> + regulator-name = "1.215V";
> + regulator-min-microvolt = <1215000>;
> + regulator-max-microvolt = <1235000>;
> + };
> + ldo2 {
> + regulator-name = "2.8V CPU";
> + regulator-min-microvolt = <2800000>;
> + regulator-max-microvolt = <2800000>;
> + regulator-always-on;
> + regulator-boot-on;
> + };
> + };
> +
> + backlight {
> + compatible = "ams,as3711-bl";
> + su2-dev = <&lcdc>;
> + su2-max-uA = <36000>;
> + su2-feedback-curr-auto;
> + su2-fbprot-gpio4;
> + su2-auto-curr1;
> + su2-auto-curr2;
> + su2-auto-curr3;
> + };
> +};
> diff --git a/drivers/mfd/as3711.c b/drivers/mfd/as3711.c
> index e994c96..5e0e8b3 100644
> --- a/drivers/mfd/as3711.c
> +++ b/drivers/mfd/as3711.c
> @@ -112,16 +112,37 @@ static const struct regmap_config as3711_regmap_config = {
> .cache_type = REGCACHE_RBTREE,
> };
>
> +#ifdef CONFIG_OF
> +static struct of_device_id as3711_of_match[] = {
> + {.compatible = "ams,as3711",},
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, as3711_of_match);
> +#endif
> +
> static int as3711_i2c_probe(struct i2c_client *client,
> const struct i2c_device_id *id)
> {
> struct as3711 *as3711;
> - struct as3711_platform_data *pdata = client->dev.platform_data;
> + struct as3711_platform_data *pdata;
> unsigned int id1, id2;
> int ret;
>
> - if (!pdata)
> - dev_dbg(&client->dev, "Platform data not found\n");
> + if (!client->dev.of_node) {
> + pdata = client->dev.platform_data;
> + if (!pdata)
> + dev_dbg(&client->dev, "Platform data not found\n");
> + } else {
> + if (!of_device_is_available(client->dev.of_node))
> + return -ENODEV;
> +
> + pdata = devm_kzalloc(&client->dev,
> + sizeof(*pdata), GFP_KERNEL);
> + if (!pdata) {
> + dev_err(&client->dev, "Failed to allocate pdata\n");
> + return -ENOMEM;
> + }
> + }
>
> as3711 = devm_kzalloc(&client->dev, sizeof(struct as3711), GFP_KERNEL);
> if (!as3711) {
> @@ -193,7 +214,8 @@ static struct i2c_driver as3711_i2c_driver = {
> .driver = {
> .name = "as3711",
> .owner = THIS_MODULE,
> - },
> + .of_match_table = of_match_ptr(as3711_of_match),
> + },
> .probe = as3711_i2c_probe,
> .remove = as3711_i2c_remove,
> .id_table = as3711_i2c_id,
> diff --git a/drivers/regulator/as3711-regulator.c b/drivers/regulator/as3711-regulator.c
> index f0ba8c4..cf145fc 100644
> --- a/drivers/regulator/as3711-regulator.c
> +++ b/drivers/regulator/as3711-regulator.c
> @@ -13,9 +13,11 @@
> #include <linux/init.h>
> #include <linux/mfd/as3711.h>
> #include <linux/module.h>
> +#include <linux/of.h>
> #include <linux/platform_device.h>
> #include <linux/regmap.h>
> #include <linux/regulator/driver.h>
> +#include <linux/regulator/of_regulator.h>
> #include <linux/slab.h>
>
> struct as3711_regulator_info {
> @@ -276,6 +278,57 @@ static struct as3711_regulator_info as3711_reg_info[] = {
>
> #define AS3711_REGULATOR_NUM ARRAY_SIZE(as3711_reg_info)
>
> +static const char *as3711_regulator_of_names[AS3711_REGULATOR_NUM] = {
> + [AS3711_REGULATOR_SD_1] = "sd1",
> + [AS3711_REGULATOR_SD_2] = "sd2",
> + [AS3711_REGULATOR_SD_3] = "sd3",
> + [AS3711_REGULATOR_SD_4] = "sd4",
> + [AS3711_REGULATOR_LDO_1] = "ldo1",
> + [AS3711_REGULATOR_LDO_2] = "ldo2",
> + [AS3711_REGULATOR_LDO_3] = "ldo3",
> + [AS3711_REGULATOR_LDO_4] = "ldo4",
> + [AS3711_REGULATOR_LDO_5] = "ldo5",
> + [AS3711_REGULATOR_LDO_6] = "ldo6",
> + [AS3711_REGULATOR_LDO_7] = "ldo7",
> + [AS3711_REGULATOR_LDO_8] = "ldo8",
> +};
> +
> +static int as3711_regulator_parse_dt(struct device *dev)
> +{
> + struct as3711_regulator_pdata *pdata = dev_get_platdata(dev);
> + struct device_node *regulators > + of_find_node_by_name(dev->parent->of_node, "regulators");
> + struct of_regulator_match *matches, *match;
> + const int count = AS3711_REGULATOR_NUM;
> + int ret, i;
> +
> + if (!regulators) {
> + dev_err(dev, "regulator node not found\n");
> + return -ENODEV;
> + }
> +
> + matches = devm_kzalloc(dev, sizeof(*matches) * count, GFP_KERNEL);
> + if (!matches)
> + return -ENOMEM;
> +
> + for (i = 0, match = matches; i < count; i++, match++) {
> + match->name = as3711_regulator_of_names[i];
> + match->driver_data = as3711_reg_info + i;
> + }
> +
> + ret = of_regulator_match(dev->parent, regulators, matches, count);
> + if (ret < 0) {
> + dev_err(dev, "Error parsing regulator init data: %d\n", ret);
> + return ret;
> + }
> +
> + for (i = 0, match = matches; i < count; i++, match++)
> + if (match->of_node)
> + pdata->init_data[i] = match->init_data;
> +
> + return 0;
> +}
> +
> static int as3711_regulator_probe(struct platform_device *pdev)
> {
> struct as3711_regulator_pdata *pdata = dev_get_platdata(&pdev->dev);
> @@ -289,8 +342,18 @@ static int as3711_regulator_probe(struct platform_device *pdev)
> int ret;
> int id;
>
> - if (!pdata)
> - dev_dbg(&pdev->dev, "No platform data...\n");
> + if (!pdata) {
> + dev_err(&pdev->dev, "No platform data...\n");
> + return -ENODEV;
> + }
> +
> + if (pdev->dev.parent->of_node) {
> + ret = as3711_regulator_parse_dt(&pdev->dev);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
> + return ret;
> + }
> + }
>
> regs = devm_kzalloc(&pdev->dev, AS3711_REGULATOR_NUM *
> sizeof(struct as3711_regulator), GFP_KERNEL);
> @@ -300,7 +363,7 @@ static int as3711_regulator_probe(struct platform_device *pdev)
> }
>
> for (id = 0, ri = as3711_reg_info; id < AS3711_REGULATOR_NUM; ++id, ri++) {
> - reg_data = pdata ? pdata->init_data[id] : NULL;
> + reg_data = pdata->init_data[id];
>
> /* No need to register if there is no regulator data */
> if (!reg_data)
> diff --git a/drivers/video/backlight/as3711_bl.c b/drivers/video/backlight/as3711_bl.c
> index c6bc65d..90c9208 100644
> --- a/drivers/video/backlight/as3711_bl.c
> +++ b/drivers/video/backlight/as3711_bl.c
> @@ -257,6 +257,109 @@ static int as3711_bl_register(struct platform_device *pdev,
> return 0;
> }
>
> +static int as3711_backlight_parse_dt(struct device *dev)
> +{
> + struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
> + struct device_node *bl > + of_find_node_by_name(dev->parent->of_node, "backlight"), *fb;
> + int ret;
> +
> + if (!bl) {
> + dev_dbg(dev, "backlight node not found\n");
> + return -ENODEV;
> + }
> +
> + fb = of_parse_phandle(bl, "su1-dev", 0);
> + if (fb) {
> + pdata->su1_fb = fb->full_name;
> +
> + ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
> + if (pdata->su1_max_uA <= 0)
> + ret = -EINVAL;
> + if (ret < 0)
> + return ret;
> + }
> +
> + fb = of_parse_phandle(bl, "su2-dev", 0);
> + if (fb) {
> + int count = 0;
> +
> + pdata->su2_fb = fb->full_name;
> +
> + ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA);
> + if (pdata->su2_max_uA <= 0)
> + ret = -EINVAL;
> + if (ret < 0)
> + return ret;
> +
> + if (of_find_property(bl, "su2-feedback-voltage", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_VOLTAGE;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr1", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR1;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr2", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR2;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr3", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR3;
> + count++;
> + }
> + if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) {
> + pdata->su2_feedback = AS3711_SU2_CURR_AUTO;
> + count++;
> + }
> + if (count != 1)
> + return -EINVAL;
> +
> + count = 0;
> + if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_LX_SD4;
> + count++;
> + }
> + if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_GPIO2;
> + count++;
> + }
> + if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_GPIO3;
> + count++;
> + }
> + if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) {
> + pdata->su2_fbprot = AS3711_SU2_GPIO4;
> + count++;
> + }
> + if (count != 1)
> + return -EINVAL;
> +
> + count = 0;
> + if (of_find_property(bl, "su2-auto-curr1", NULL)) {
> + pdata->su2_auto_curr1 = true;
> + count++;
> + }
> + if (of_find_property(bl, "su2-auto-curr2", NULL)) {
> + pdata->su2_auto_curr2 = true;
> + count++;
> + }
> + if (of_find_property(bl, "su2-auto-curr3", NULL)) {
> + pdata->su2_auto_curr3 = true;
> + count++;
> + }
> +
> + /*
> + * At least one su2-auto-curr* must be specified iff
> + * AS3711_SU2_CURR_AUTO is used
> + */
> + if (!count ^ pdata->su2_feedback != AS3711_SU2_CURR_AUTO)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> static int as3711_backlight_probe(struct platform_device *pdev)
> {
> struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
> @@ -266,11 +369,24 @@ static int as3711_backlight_probe(struct platform_device *pdev)
> unsigned int max_brightness;
> int ret;
>
> - if (!pdata || (!pdata->su1_fb && !pdata->su2_fb)) {
> + if (!pdata) {
> dev_err(&pdev->dev, "No platform data, exiting...\n");
> return -ENODEV;
> }
>
> + if (pdev->dev.parent->of_node) {
> + ret = as3711_backlight_parse_dt(&pdev->dev);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
> + return ret;
> + }
> + }
> +
> + if (!pdata->su1_fb && !pdata->su2_fb) {
> + dev_err(&pdev->dev, "No framebuffer specified\n");
> + return -EINVAL;
> + }
> +
> /*
> * Due to possible hardware damage I chose to block all modes,
> * unsupported on my hardware. Anyone, wishing to use any of those modes
> --
> 1.7.2.5
>
^ permalink raw reply
* Re: [PATCH v2] fbdev: Add Renesas vdc4 framebuffer driver
From: Simon Horman @ 2013-02-16 2:04 UTC (permalink / raw)
To: linux-fbdev
In-Reply-To: <1347267372-22949-1-git-send-email-phil.edworthy@renesas.com>
On Fri, Oct 05, 2012 at 05:34:54PM +0100, phil.edworthy@renesas.com wrote:
> Hi,
>
> Any news on this patch?
Phil, did this patch get merged in the end?
>
> Thanks
> Phil
>
> > From: Jingoo Han <jg1.han@samsung.com>
> > To: "'Phil Edworthy'" <phil.edworthy@renesas.com>,
> > Cc: "'Florian Tobias Schandinat'" <FlorianSchandinat@gmx.de>, linux-
> > fbdev@vger.kernel.org, linux-sh@vger.kernel.org, "'Jingoo Han'"
> > <jg1.han@samsung.com>
> > Date: 11/09/2012 03:31
> > Subject: Re: [PATCH v2] fbdev: Add Renesas vdc4 framebuffer driver
> >
> > On Monday, September 10, 2012 5:56 PM Phil Edworthy wrote
> > >
> > > The vdc4 display hardware is found on the sh7269 device.
> > >
> > > Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
> >
> >
> > Reviewed-by: Jingoo Han <jg1.han@samsung.com>
> >
> > Best regards,
> > Jingoo Han
> >
> >
> > > ---
> > > v2:
> > > * Use devm_ variants of clk_get, ioremap_nocache, request_irq.
> > > * Replace spaces with tabs.
> > > * Check ren_vdc4_start return value.
> > > * Fix headers used.
> > >
> > > drivers/video/Kconfig | 10 +
> > > drivers/video/Makefile | 1 +
> > > drivers/video/ren_vdc4fb.c | 641 +++++++++++++++++++++++++++++++
> > +++++++++++++
> > > include/video/ren_vdc4fb.h | 19 ++
> > > 4 files changed, 671 insertions(+), 0 deletions(-)
> > > create mode 100644 drivers/video/ren_vdc4fb.c
> > > create mode 100644 include/video/ren_vdc4fb.h
> >
> >
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-sh" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
* [PATCH RFC] video: Add Hyper-V Synthetic Video Frame Buffer Driver
From: Haiyang Zhang @ 2013-02-15 18:48 UTC (permalink / raw)
To: FlorianSchandinat, linux-fbdev
Cc: haiyangz, kys, olaf, jasowang, linux-kernel, devel
This is the driver for the Hyper-V Synthetic Video, which supports screen
resolution up to Full HD 1920x1080 on Windows Server 2012 host, and 1600x1200
on Windows Server 2008 R2 or earlier.
It also solves the double mouse cursor issue of the emulated video mode.
Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
Reviewed-by: K. Y. Srinivasan <kys@microsoft.com>
---
drivers/video/Kconfig | 11 +
drivers/video/Makefile | 1 +
drivers/video/hyperv_fb.c | 983 +++++++++++++++++++++++++++++++++++++++++++++
drivers/video/vesafb.c | 48 +++
include/linux/hyperv.h | 11 +
5 files changed, 1054 insertions(+), 0 deletions(-)
create mode 100644 drivers/video/hyperv_fb.c
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 1e2f2d8..7d2a721 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2449,6 +2449,17 @@ config FB_PUV3_UNIGFX
Choose this option if you want to use the Unigfx device as a
framebuffer device. Without the support of PCI & AGP.
+config HYPERV_FB
+ tristate "Microsoft Hyper-V Synthetic Video support"
+ depends on FB && HYPERV
+ select FB_SYS_FILLRECT
+ select FB_SYS_COPYAREA
+ select FB_SYS_IMAGEBLIT
+ select FB_SYS_FOPS
+ select FB_DEFERRED_IO
+ help
+ This framebuffer driver supports Microsoft Hyper-V Synthetic Video.
+
source "drivers/video/omap/Kconfig"
source "drivers/video/omap2/Kconfig"
source "drivers/video/exynos/Kconfig"
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 136af7d..dd1c6da 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -148,6 +148,7 @@ obj-$(CONFIG_FB_MSM) += msm/
obj-$(CONFIG_FB_NUC900) += nuc900fb.o
obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o
obj-$(CONFIG_FB_PUV3_UNIGFX) += fb-puv3.o
+obj-$(CONFIG_HYPERV_FB) += hyperv_fb.o
# Platform or fallback drivers go here
obj-$(CONFIG_FB_UVESA) += uvesafb.o
diff --git a/drivers/video/hyperv_fb.c b/drivers/video/hyperv_fb.c
new file mode 100644
index 0000000..d7c660f
--- /dev/null
+++ b/drivers/video/hyperv_fb.c
@@ -0,0 +1,983 @@
+/*
+ * Copyright (c) 2012, Microsoft Corporation.
+ *
+ * Author:
+ * Haiyang Zhang <haiyangz@microsoft.com>
+ *
+ * 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, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ */
+
+/*
+ * Hyper-V Synthetic Video Frame Buffer Driver
+ *
+ * This is the driver for the Hyper-V Synthetic Video, which supports
+ * screen resolution up to Full HD 1920x1080 with 32 bit color on Windows
+ * Server 2012, and 1600x1200 with 16 bit color on Windows Server 2008 R2
+ * or earlier.
+ *
+ * It also solves the double mouse cursor issue of the emulated video mode.
+ *
+ * The default screen resolution is 1152x864, which may be changed by a
+ * kernel parameter:
+ * video=hyperv_fb:<width>x<height>
+ * For example: video=hyperv_fb:1280x1024
+ *
+ * Portrait orientation is also supported:
+ * For example: video=hyperv_fb:864x1152
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/completion.h>
+#include <linux/fb.h>
+
+#include <linux/hyperv.h>
+
+
+/* Hyper-V Synthetic Video Protocol definitions and structures */
+#define MAX_VMBUS_PKT_SIZE 0x4000
+
+#define SYNTHVID_VERSION(major, minor) ((minor) << 16 | (major))
+#define SYNTHVID_VERSION_WIN7 SYNTHVID_VERSION(3, 0)
+#define SYNTHVID_VERSION_WIN8 SYNTHVID_VERSION(3, 2)
+
+#define SYNTHVID_DEPTH_WIN7 16
+#define SYNTHVID_DEPTH_WIN8 32
+
+#define SYNTHVID_FB_SIZE_WIN7 (4 * 1024 * 1024)
+#define SYNTHVID_WIDTH_MAX_WIN7 1600
+#define SYNTHVID_HEIGHT_MAX_WIN7 1200
+
+#define SYNTHVID_FB_SIZE_WIN8 (8 * 1024 * 1024)
+
+
+enum pipe_msg_type {
+ PIPE_MSG_INVALID,
+ PIPE_MSG_DATA,
+ PIPE_MSG_MAX
+};
+
+struct pipe_msg_hdr {
+ u32 type;
+ u32 size; /* size of message after this field */
+} __packed;
+
+
+enum synthvid_msg_type {
+ SYNTHVID_ERROR = 0,
+ SYNTHVID_VERSION_REQUEST = 1,
+ SYNTHVID_VERSION_RESPONSE = 2,
+ SYNTHVID_VRAM_LOCATION = 3,
+ SYNTHVID_VRAM_LOCATION_ACK = 4,
+ SYNTHVID_SITUATION_UPDATE = 5,
+ SYNTHVID_SITUATION_UPDATE_ACK = 6,
+ SYNTHVID_POINTER_POSITION = 7,
+ SYNTHVID_POINTER_SHAPE = 8,
+ SYNTHVID_FEATURE_CHANGE = 9,
+ SYNTHVID_DIRT = 10,
+
+ SYNTHVID_MAX = 11
+};
+
+struct synthvid_msg_hdr {
+ u32 type;
+ u32 size; /* size of this header + payload after this field*/
+} __packed;
+
+
+struct synthvid_version_req {
+ u32 version;
+} __packed;
+
+struct synthvid_version_resp {
+ u32 version;
+ u8 is_accepted;
+ u8 max_video_outputs;
+} __packed;
+
+struct synthvid_vram_location {
+ u64 user_ctx;
+ u8 is_vram_gpa_specified;
+ u64 vram_gpa;
+} __packed;
+
+struct synthvid_vram_location_ack {
+ u64 user_ctx;
+} __packed;
+
+struct video_output_situation {
+ u8 active;
+ u32 vram_offset;
+ u8 depth_bits;
+ u32 width_pixels;
+ u32 height_pixels;
+ u32 pitch_bytes;
+} __packed;
+
+struct synthvid_situation_update {
+ u64 user_ctx;
+ u8 video_output_count;
+ struct video_output_situation video_output[1];
+} __packed;
+
+struct synthvid_situation_update_ack {
+ u64 user_ctx;
+} __packed;
+
+struct synthvid_pointer_position {
+ u8 is_visible;
+ u8 video_output;
+ s32 image_x;
+ s32 image_y;
+} __packed;
+
+
+#define CURSOR_MAX_X 96
+#define CURSOR_MAX_Y 96
+#define CURSOR_ARGB_PIXEL_SIZE 4
+#define CURSOR_MAX_SIZE (CURSOR_MAX_X * CURSOR_MAX_Y * CURSOR_ARGB_PIXEL_SIZE)
+#define CURSOR_COMPLETE (-1)
+
+struct synthvid_pointer_shape {
+ u8 part_idx;
+ u8 is_argb;
+ u32 width; /* CURSOR_MAX_X at most */
+ u32 height; /* CURSOR_MAX_Y at most */
+ u32 hot_x; /* hotspot relative to upper-left of pointer image */
+ u32 hot_y;
+ u8 data[4];
+} __packed;
+
+struct synthvid_feature_change {
+ u8 is_dirt_needed;
+ u8 is_ptr_pos_needed;
+ u8 is_ptr_shape_needed;
+ u8 is_situ_needed;
+} __packed;
+
+struct rect {
+ s32 x1, y1; /* top left corner */
+ s32 x2, y2; /* bottom right corner, exclusive */
+} __packed;
+
+struct synthvid_dirt {
+ u8 video_output;
+ u8 dirt_count;
+ struct rect rect[1];
+} __packed;
+
+struct synthvid_msg {
+ struct pipe_msg_hdr pipe_hdr;
+ struct synthvid_msg_hdr vid_hdr;
+ union {
+ struct synthvid_version_req ver_req;
+ struct synthvid_version_resp ver_resp;
+ struct synthvid_vram_location vram;
+ struct synthvid_vram_location_ack vram_ack;
+ struct synthvid_situation_update situ;
+ struct synthvid_situation_update_ack situ_ack;
+ struct synthvid_pointer_position ptr_pos;
+ struct synthvid_pointer_shape ptr_shape;
+ struct synthvid_feature_change feature_chg;
+ struct synthvid_dirt dirt;
+ };
+} __packed;
+
+
+
+/* FB driver definitions and structures */
+#define HVFB_WIDTH 1152 /* default screen width */
+#define HVFB_HEIGHT 864 /* default screen height */
+#define HVFB_WIDTH_MIN 640
+#define HVFB_HEIGHT_MIN 480
+
+#define RING_BUFSIZE (256 * 1024)
+#define VSP_TIMEOUT (10 * HZ)
+#define HVFB_UPDATE_DELAY (HZ / 30)
+
+struct hvfb_par {
+ struct fb_info *info;
+ bool info_ready; /* info and its fields are set up */
+ struct completion wait;
+ u32 synthvid_version;
+
+ struct delayed_work dwork;
+ spinlock_t area_lock; /* protect changed area */
+ int x1, y1, x2, y2; /* changed rectangle area */
+
+ u32 pseudo_palette[16];
+ u8 init_buf[MAX_VMBUS_PKT_SIZE];
+ u8 recv_buf[MAX_VMBUS_PKT_SIZE];
+};
+
+static uint screen_width = HVFB_WIDTH;
+static uint screen_height = HVFB_HEIGHT;
+static uint screen_depth;
+static uint screen_fb_size;
+
+/* Send message to Hyper-V host */
+static inline int synthvid_send(struct hv_device *hdev,
+ struct synthvid_msg *msg)
+{
+ static atomic64_t request_id = ATOMIC64_INIT(0);
+ int ret;
+
+ msg->pipe_hdr.type = PIPE_MSG_DATA;
+ msg->pipe_hdr.size = msg->vid_hdr.size;
+
+ ret = vmbus_sendpacket(hdev->channel, msg,
+ msg->vid_hdr.size + sizeof(struct pipe_msg_hdr),
+ atomic64_inc_return(&request_id),
+ VM_PKT_DATA_INBAND, 0);
+
+ if (ret)
+ pr_err("Unable to send packet via vmbus\n");
+
+ return ret;
+}
+
+
+/* Send screen resolution info to host */
+static int synthvid_send_situ(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par;
+ struct synthvid_msg msg;
+
+ if (!info)
+ return -ENODEV;
+
+ par = info->par;
+ if (!par->info_ready)
+ return -ENODEV;
+
+ memset(&msg, 0, sizeof(struct synthvid_msg));
+
+ msg.vid_hdr.type = SYNTHVID_SITUATION_UPDATE;
+ msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ sizeof(struct synthvid_situation_update);
+ msg.situ.user_ctx = 0;
+ msg.situ.video_output_count = 1;
+ msg.situ.video_output[0].active = 1;
+ msg.situ.video_output[0].vram_offset = 0;
+ msg.situ.video_output[0].depth_bits = info->var.bits_per_pixel;
+ msg.situ.video_output[0].width_pixels = info->var.xres;
+ msg.situ.video_output[0].height_pixels = info->var.yres;
+ msg.situ.video_output[0].pitch_bytes = info->fix.line_length;
+
+ synthvid_send(hdev, &msg);
+
+ return 0;
+}
+
+/* Send mouse pointer info to host */
+static int synthvid_send_ptr(struct hv_device *hdev)
+{
+ struct synthvid_msg msg;
+
+ memset(&msg, 0, sizeof(struct synthvid_msg));
+ msg.vid_hdr.type = SYNTHVID_POINTER_POSITION;
+ msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ sizeof(struct synthvid_pointer_position);
+ msg.ptr_pos.is_visible = 1;
+ msg.ptr_pos.video_output = 0;
+ msg.ptr_pos.image_x = 0;
+ msg.ptr_pos.image_y = 0;
+ synthvid_send(hdev, &msg);
+
+ memset(&msg, 0, sizeof(struct synthvid_msg));
+ msg.vid_hdr.type = SYNTHVID_POINTER_SHAPE;
+ msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ sizeof(struct synthvid_pointer_shape);
+ msg.ptr_shape.part_idx = CURSOR_COMPLETE;
+ msg.ptr_shape.is_argb = 1;
+ msg.ptr_shape.width = 1;
+ msg.ptr_shape.height = 1;
+ msg.ptr_shape.hot_x = 0;
+ msg.ptr_shape.hot_y = 0;
+ msg.ptr_shape.data[0] = 0;
+ msg.ptr_shape.data[1] = 1;
+ msg.ptr_shape.data[2] = 1;
+ msg.ptr_shape.data[3] = 1;
+ synthvid_send(hdev, &msg);
+
+ return 0;
+}
+
+/* Send updated screen area (dirty rectangle) location to host */
+static int synthvid_update(struct fb_info *info, int x1, int y1,
+ int x2, int y2)
+{
+ struct hv_device *hdev = device_to_hv_device(info->device);
+ struct synthvid_msg msg;
+
+ memset(&msg, 0, sizeof(struct synthvid_msg));
+
+ msg.vid_hdr.type = SYNTHVID_DIRT;
+ msg.vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ sizeof(struct synthvid_dirt);
+ msg.dirt.video_output = 0;
+ msg.dirt.dirt_count = 1;
+ msg.dirt.rect[0].x1 = x1;
+ msg.dirt.rect[0].y1 = y1;
+ msg.dirt.rect[0].x2 = x2;
+ msg.dirt.rect[0].y2 = y2;
+
+ synthvid_send(hdev, &msg);
+
+ return 0;
+}
+
+
+/*
+ * Actions on received messages from host:
+ * Complete the wait event.
+ * Or, reply with screen and cursor info.
+ */
+static void synthvid_recv_sub(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par;
+ struct synthvid_msg *msg;
+
+ if (!info)
+ return;
+
+ par = info->par;
+ msg = (struct synthvid_msg *)par->recv_buf;
+
+ /* Complete the wait event */
+ if (msg->vid_hdr.type = SYNTHVID_VERSION_RESPONSE ||
+ msg->vid_hdr.type = SYNTHVID_VRAM_LOCATION_ACK) {
+ memcpy(par->init_buf, msg, MAX_VMBUS_PKT_SIZE);
+ complete(&par->wait);
+ return;
+ }
+
+ /* Reply with screen and cursor info */
+ if (msg->vid_hdr.type = SYNTHVID_FEATURE_CHANGE) {
+ synthvid_send_situ(hdev);
+ synthvid_send_ptr(hdev);
+ }
+
+}
+
+/* Receive callback for messages from the host */
+static void synthvid_receive(void *ctx)
+{
+ struct hv_device *hdev = ctx;
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par;
+ struct synthvid_msg *recv_buf;
+ u32 bytes_recvd;
+ u64 req_id;
+ int ret;
+
+ if (!info)
+ return;
+
+ par = info->par;
+ recv_buf = (struct synthvid_msg *)par->recv_buf;
+
+ do {
+ ret = vmbus_recvpacket(hdev->channel, recv_buf,
+ MAX_VMBUS_PKT_SIZE,
+ &bytes_recvd, &req_id);
+ if (bytes_recvd > 0 &&
+ recv_buf->pipe_hdr.type = PIPE_MSG_DATA)
+ synthvid_recv_sub(hdev);
+ } while (bytes_recvd > 0 && ret = 0);
+}
+
+/* Check synthetic video protocol version with the host */
+static int synthvid_negotiate_ver(struct hv_device *hdev, u32 ver)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par = info->par;
+ struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf;
+ int t, ret = 0;
+
+ memset(msg, 0, sizeof(struct synthvid_msg));
+ msg->vid_hdr.type = SYNTHVID_VERSION_REQUEST;
+ msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ sizeof(struct synthvid_version_req);
+ msg->ver_req.version = ver;
+ synthvid_send(hdev, msg);
+
+ t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT);
+ if (!t) {
+ pr_err("Time out on waiting version response\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ if (!msg->ver_resp.is_accepted) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ par->synthvid_version = ver;
+
+out:
+ return ret;
+}
+
+/* Connect to VSP (Virtual Service Provider) on host */
+static int synthvid_connect_vsp(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par = info->par;
+ int ret;
+
+ ret = vmbus_open(hdev->channel, RING_BUFSIZE, RING_BUFSIZE,
+ NULL, 0, synthvid_receive, hdev);
+ if (ret) {
+ pr_err("Unable to open vmbus channel\n");
+ return ret;
+ }
+
+ /* Negotiate the protocol version with host */
+ if (vmbus_proto_version = VERSION_WS2008 ||
+ vmbus_proto_version = VERSION_WIN7)
+ ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN7);
+ else
+ ret = synthvid_negotiate_ver(hdev, SYNTHVID_VERSION_WIN8);
+
+ if (ret) {
+ pr_err("Synthetic video device version not accepted\n");
+ goto error;
+ }
+
+ if (par->synthvid_version = SYNTHVID_VERSION_WIN7) {
+ screen_depth = SYNTHVID_DEPTH_WIN7;
+ screen_fb_size = SYNTHVID_FB_SIZE_WIN7;
+ } else {
+ screen_depth = SYNTHVID_DEPTH_WIN8;
+ screen_fb_size = SYNTHVID_FB_SIZE_WIN8;
+ }
+
+ return 0;
+
+error:
+ vmbus_close(hdev->channel);
+ return ret;
+}
+
+/* Send VRAM and Situation messages to the host */
+static int synthvid_send_config(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ struct hvfb_par *par = info->par;
+ struct synthvid_msg *msg = (struct synthvid_msg *)par->init_buf;
+ int t, ret = 0;
+
+ /* Send VRAM location */
+ memset(msg, 0, sizeof(struct synthvid_msg));
+ msg->vid_hdr.type = SYNTHVID_VRAM_LOCATION;
+ msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
+ sizeof(struct synthvid_vram_location);
+ msg->vram.user_ctx = msg->vram.vram_gpa + virt_to_phys(info->screen_base);
+ msg->vram.is_vram_gpa_specified = 1;
+ synthvid_send(hdev, msg);
+
+ t = wait_for_completion_timeout(&par->wait, VSP_TIMEOUT);
+ if (!t) {
+ pr_err("Time out on waiting vram location ack\n");
+ ret = -ETIMEDOUT;
+ goto error;
+ }
+ if (msg->vram_ack.user_ctx != virt_to_phys(info->screen_base)) {
+ pr_err("Unable to set VRAM location\n");
+ ret = -ENODEV;
+ goto error;
+ }
+
+ /* Send situation and pointer update */
+ synthvid_send_situ(hdev);
+ synthvid_send_ptr(hdev);
+
+ return 0;
+
+error:
+ return ret;
+}
+
+
+static inline void hvfb_init_rect(struct hvfb_par *par)
+{
+ par->x1 = par->y1 = INT_MAX;
+ par->x2 = par->y2 = 0;
+}
+
+/*
+ * Delayed work callback:
+ * It is called at HVFB_UPDATE_DELAY or longer time interval to process
+ * screen updates from non-deferred I/O, such as imageblit, which can happen
+ * at high frequency.
+ */
+static void hvfb_update_work(struct work_struct *w)
+{
+ struct hvfb_par *par = container_of(w, struct hvfb_par, dwork.work);
+ struct fb_info *info = par->info;
+ int x1, y1, x2, y2;
+ ulong flags;
+
+ spin_lock_irqsave(&par->area_lock, flags);
+
+ x1 = par->x1;
+ y1 = par->y1;
+ x2 = par->x2;
+ y2 = par->y2;
+
+ hvfb_init_rect(par);
+
+ spin_unlock_irqrestore(&par->area_lock, flags);
+
+ if (x1 = INT_MAX)
+ return;
+
+ synthvid_update(info, x1, y1, x2, y2);
+}
+
+/*
+ * Record the updated screen area, and schedule the delayed work.
+ * If the work is still on the queue, schedule_delayed_work() will
+ * automatically ignore the new request. But the updated area is
+ * always recorded, and will be handled by the queued work.
+ */
+static void hvfb_add_rect(struct fb_info *info, int x1, int y1,
+ int x2, int y2)
+{
+ struct hvfb_par *par = info->par;
+ ulong flags;
+
+ spin_lock_irqsave(&par->area_lock, flags);
+
+ if (x1 < par->x1)
+ par->x1 = x1;
+ if (y1 < par->y1)
+ par->y1 = y1;
+
+ if (x2 > par->x2)
+ par->x2 = x2;
+ if (y2 > par->y2)
+ par->y2 = y2;
+
+ spin_unlock_irqrestore(&par->area_lock, flags);
+
+ schedule_delayed_work(&par->dwork, HVFB_UPDATE_DELAY);
+}
+
+
+
+/* Framebuffer operation handlers */
+
+static ssize_t hvfb_write(struct fb_info *info, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t ret;
+
+ ret = fb_sys_write(info, buf, count, ppos);
+
+ hvfb_add_rect(info, 0, 0,
+ info->var.xres, info->var.yres);
+
+ return ret;
+}
+
+
+static int hvfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ if (var->xres < HVFB_WIDTH_MIN || var->yres < HVFB_HEIGHT_MIN ||
+ var->xres > screen_width || var->yres > screen_height ||
+ var->bits_per_pixel != screen_depth)
+ return -EINVAL;
+
+ var->xres_virtual = var->xres;
+ var->yres_virtual = var->yres;
+
+ return 0;
+}
+
+static int hvfb_set_par(struct fb_info *info)
+{
+ struct hv_device *hdev = device_to_hv_device(info->device);
+
+ return synthvid_send_situ(hdev);
+}
+
+
+static inline u32 chan_to_field(u32 chan, struct fb_bitfield *bf)
+{
+ return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset;
+}
+
+static int hvfb_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *info)
+{
+ u32 *pal = info->pseudo_palette;
+
+ if (regno > 15)
+ return -EINVAL;
+
+ pal[regno] = chan_to_field(red, &info->var.red)
+ | chan_to_field(green, &info->var.green)
+ | chan_to_field(blue, &info->var.blue);
+
+ return 0;
+}
+
+static void hvfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
+{
+ sys_fillrect(info, rect);
+
+ hvfb_add_rect(info, rect->dx, rect->dy,
+ rect->dx + rect->width, rect->dy + rect->height);
+}
+
+static void hvfb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
+{
+ sys_copyarea(info, area);
+
+ hvfb_add_rect(info, area->dx, area->dy,
+ area->dx + area->width, area->dy + area->height);
+}
+
+static void hvfb_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+ sys_imageblit(info, image);
+
+ hvfb_add_rect(info, image->dx, image->dy,
+ image->dx + image->width, image->dy + image->height);
+}
+
+static struct fb_ops hvfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_read = fb_sys_read,
+ .fb_write = hvfb_write,
+ .fb_check_var = hvfb_check_var,
+ .fb_set_par = hvfb_set_par,
+ .fb_setcolreg = hvfb_setcolreg,
+ .fb_fillrect = hvfb_fillrect,
+ .fb_copyarea = hvfb_copyarea,
+ .fb_imageblit = hvfb_imageblit,
+};
+
+
+/*
+ * Callback for the deferred I/O.
+ * Applications, such as X-Window, use deferred I/O mechanism to put screen
+ * updates in batches, and send to fb driver periodically.
+ */
+static void hvfb_deferred_io(struct fb_info *info, struct list_head *pl)
+{
+ struct page *page;
+ ulong pa, pb, min = ULONG_MAX, max = 0;
+ int ymin, ymax;
+
+ list_for_each_entry(page, pl, lru) {
+ pa = page->index << PAGE_SHIFT;
+ pb = pa + PAGE_SIZE - 1;
+ if (pa < min)
+ min = pa;
+ if (pb > max)
+ max = pb;
+ }
+
+ if (max = 0)
+ return;
+
+ ymin = min / info->fix.line_length;
+ ymax = max / info->fix.line_length + 1;
+ if (ymax > info->var.yres)
+ ymax = info->var.yres;
+
+ synthvid_update(info, 0, ymin, info->var.xres, ymax);
+}
+
+static struct fb_deferred_io hvfb_defio = {
+ .delay = HVFB_UPDATE_DELAY,
+ .deferred_io = hvfb_deferred_io,
+};
+
+
+/* Get options from kernel paramenter "video=" */
+static void hvfb_get_option(struct fb_info *info)
+{
+ struct hvfb_par *par = info->par;
+ char *opt = NULL, *p;
+ uint x = 0, y = 0;
+
+ if (fb_get_options("hyperv_fb", &opt) || !opt || !*opt)
+ return;
+
+ p = strsep(&opt, "x");
+ if (!*p || kstrtouint(p, 0, &x) ||
+ !opt || !*opt || kstrtouint(opt, 0, &y)) {
+ pr_err("Screen option is invalid: skipped\n");
+ return;
+ }
+
+ if (x < HVFB_WIDTH_MIN || y < HVFB_HEIGHT_MIN ||
+ (par->synthvid_version = SYNTHVID_VERSION_WIN8 &&
+ x * y * screen_depth / 8 > SYNTHVID_FB_SIZE_WIN8) ||
+ (par->synthvid_version = SYNTHVID_VERSION_WIN7 &&
+ (x > SYNTHVID_WIDTH_MAX_WIN7 || y > SYNTHVID_HEIGHT_MAX_WIN7))) {
+ pr_err("Screen resolution option is out of range: skipped\n");
+ return;
+ }
+
+ screen_width = x;
+ screen_height = y;
+ return;
+}
+
+
+/* Allocate framebuffer memory */
+#define FOUR_MEGA (4*1024*1024)
+#define NALLOC 10
+static void *hvfb_getmem(void)
+{
+ ulong *p;
+ int i, j;
+ ulong ret = 0;
+
+ if (screen_fb_size = FOUR_MEGA) {
+ ret = __get_free_pages(GFP_KERNEL|__GFP_ZERO,
+ get_order(FOUR_MEGA));
+ goto out1;
+ }
+
+ if (screen_fb_size != FOUR_MEGA * 2)
+ return NULL;
+
+ /*
+ * Windows 2012 requires frame buffer size to be 8MB, which exceeds
+ * the limit of __get_free_pages(). So, we allocate multiple 4MB
+ * chunks, and find out two adjacent ones.
+ */
+ p = kmalloc(NALLOC * sizeof(ulong), GFP_KERNEL);
+ if (!p)
+ return NULL;
+
+ for (i = 0; i < NALLOC; i++)
+ p[i] = __get_free_pages(GFP_KERNEL|__GFP_ZERO,
+ get_order(FOUR_MEGA));
+
+ for (i = 0; i < NALLOC; i++)
+ for (j = 0; j < NALLOC; j++) {
+ if (p[j] && p[i] && virt_to_phys((void *)p[j]) -
+ virt_to_phys((void *)p[i]) = FOUR_MEGA &&
+ p[j] - p[i] = FOUR_MEGA) {
+ ret = p[i];
+ goto out;
+ }
+ }
+
+out:
+ for (i = 0; i < NALLOC; i++)
+ if (p[i] && !(ret && (p[i] = ret || p[i] = ret + FOUR_MEGA)))
+ free_pages(p[i], get_order(FOUR_MEGA));
+
+ kfree(p);
+
+out1:
+ if (!ret)
+ return NULL;
+
+ /* Get an extra ref-count to prevent page error when x-window exits */
+ for (i = 0; i < screen_fb_size; i += PAGE_SIZE)
+ atomic_inc(&virt_to_page((void *)ret + i)->_count);
+
+ return (void *)ret;
+}
+
+/* Free the frame buffer */
+static void hvfb_putmem(void *p)
+{
+ int i;
+
+ if (!p)
+ return;
+
+ for (i = 0; i < screen_fb_size; i += PAGE_SIZE)
+ atomic_dec(&virt_to_page(p + i)->_count);
+
+ free_pages((ulong)p, get_order(FOUR_MEGA));
+
+ if (screen_fb_size = FOUR_MEGA * 2)
+ free_pages((ulong)p + FOUR_MEGA, get_order(FOUR_MEGA));
+}
+
+
+static int hvfb_probe(struct hv_device *hdev,
+ const struct hv_vmbus_device_id *dev_id)
+{
+ struct fb_info *info;
+ void *fb = NULL;
+ struct hvfb_par *par;
+ int ret = 0;
+
+ info = framebuffer_alloc(sizeof(struct hvfb_par), &hdev->device);
+ if (!info) {
+ pr_err("No memory for framebuffer info\n");
+ return -ENOMEM;
+ }
+
+ par = info->par;
+ par->info = info;
+ par->info_ready = false;
+ init_completion(&par->wait);
+
+ /* Connect to VSP */
+ hv_set_drvdata(hdev, info);
+ ret = synthvid_connect_vsp(hdev);
+ if (ret) {
+ pr_err("Unable to connect to VSP\n");
+ goto error1;
+ }
+
+ fb = hvfb_getmem();
+ if (!fb) {
+ pr_err("No memory for framebuffer\n");
+ ret = -ENOMEM;
+ goto error2;
+ }
+
+ hvfb_get_option(info);
+ pr_info("Screen resolution: %dx%d, Color depth: %d\n",
+ screen_width, screen_height, screen_depth);
+
+
+ /* Set up fb_info */
+ info->flags = FBINFO_DEFAULT | FBINFO_VIRTFB;
+
+ info->var.xres_virtual = info->var.xres = screen_width;
+ info->var.yres_virtual = info->var.yres = screen_height;
+ info->var.bits_per_pixel = screen_depth;
+
+ if (info->var.bits_per_pixel = 16) {
+ info->var.red = (struct fb_bitfield){11, 5, 0};
+ info->var.green = (struct fb_bitfield){5, 6, 0};
+ info->var.blue = (struct fb_bitfield){0, 5, 0};
+
+ } else {
+ info->var.red = (struct fb_bitfield){16, 8, 0};
+ info->var.green = (struct fb_bitfield){8, 8, 0};
+ info->var.blue = (struct fb_bitfield){0, 8, 0};
+ }
+
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->var.height = -1;
+ info->var.width = -1;
+ info->var.vmode = FB_VMODE_NONINTERLACED;
+
+ strcpy(info->fix.id, "hyperv");
+ info->fix.smem_start = virt_to_phys(fb);
+ info->fix.smem_len = screen_width * screen_height * screen_depth / 8;
+ info->fix.type = FB_TYPE_PACKED_PIXELS;
+ info->fix.visual = FB_VISUAL_TRUECOLOR;
+ info->fix.line_length = screen_width * screen_depth / 8;
+ info->fix.accel = FB_ACCEL_NONE;
+
+ info->fbops = &hvfb_ops;
+ info->screen_base = fb;
+ info->pseudo_palette = par->pseudo_palette;
+
+ par->info_ready = true;
+
+ /* Send config to host */
+ ret = synthvid_send_config(hdev);
+ if (ret)
+ goto error2;
+
+ info->fbdefio = &hvfb_defio;
+ fb_deferred_io_init(info);
+
+ INIT_DELAYED_WORK(&par->dwork, hvfb_update_work);
+ spin_lock_init(&par->area_lock);
+ hvfb_init_rect(par);
+ ret = register_framebuffer(info);
+ if (ret) {
+ pr_err("Unable to register framebuffer\n");
+ goto error;
+ }
+
+ return 0;
+
+error:
+ cancel_delayed_work_sync(&par->dwork);
+ fb_deferred_io_cleanup(info);
+error2:
+ vmbus_close(hdev->channel);
+error1:
+ hv_set_drvdata(hdev, NULL);
+ framebuffer_release(info);
+ hvfb_putmem(fb);
+ return ret;
+}
+
+
+static int hvfb_remove(struct hv_device *hdev)
+{
+ struct fb_info *info = hv_get_drvdata(hdev);
+ void *fb = info->screen_base;
+ struct hvfb_par *par = info->par;
+
+ fb_deferred_io_cleanup(info);
+ unregister_framebuffer(info);
+ cancel_delayed_work_sync(&par->dwork);
+
+ vmbus_close(hdev->channel);
+ hv_set_drvdata(hdev, NULL);
+
+ framebuffer_release(info);
+ hvfb_putmem(fb);
+
+ return 0;
+}
+
+
+static const struct hv_vmbus_device_id id_table[] = {
+ /* Synthetic Video Device GUID */
+ {HV_SYNTHVID_GUID},
+ {}
+};
+
+MODULE_DEVICE_TABLE(vmbus, id_table);
+
+static struct hv_driver hvfb_drv = {
+ .name = KBUILD_MODNAME,
+ .id_table = id_table,
+ .probe = hvfb_probe,
+ .remove = hvfb_remove,
+};
+
+
+static int __init hvfb_drv_init(void)
+{
+ return vmbus_driver_register(&hvfb_drv);
+}
+
+static void __exit hvfb_drv_exit(void)
+{
+ vmbus_driver_unregister(&hvfb_drv);
+}
+
+module_init(hvfb_drv_init);
+module_exit(hvfb_drv_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_VERSION(HV_DRV_VERSION);
+MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic Video Frame Buffer Driver");
diff --git a/drivers/video/vesafb.c b/drivers/video/vesafb.c
index 501b340..34b82075 100644
--- a/drivers/video/vesafb.c
+++ b/drivers/video/vesafb.c
@@ -19,6 +19,7 @@
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/screen_info.h>
+#include <linux/dmi.h>
#include <video/vga.h>
#include <asm/io.h>
@@ -495,6 +496,41 @@ err:
return err;
}
+
+#if IS_ENABLED(CONFIG_HYPERV_FB)
+static struct dmi_system_id __initdata hyperv_dmi[] = {
+ {
+ .ident = "Microsoft Virtual PC",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "VS2005R2"),
+ },
+ },
+
+ {
+ .ident = "Microsoft Hyper-V",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"),
+ },
+ },
+
+ {}
+};
+
+/* Check if we are running on Microsoft Hyper-V platform */
+static bool __init is_hyperv(void)
+{
+ /*
+ * If the first match is "Microsoft Hyper-V", it means we are not
+ * running on the MS Virtual PC or other platforms, but on Hyper-V.
+ */
+ return dmi_first_match(hyperv_dmi) = hyperv_dmi + 1;
+}
+#endif
+
+
static struct platform_driver vesafb_driver = {
.driver = {
.name = "vesafb",
@@ -508,6 +544,18 @@ static int __init vesafb_init(void)
int ret;
char *option = NULL;
+#if IS_ENABLED(CONFIG_HYPERV_FB)
+ /*
+ * On Hyper-V both the emulated and synthetic video devices are
+ * available. To avoid conflicts, we disable vesafb for the emulated
+ * video if hyperv_fb is configured.
+ */
+ if (is_hyperv()) {
+ pr_info("Disabled vesafb on Hyper-V.\n");
+ return -ENODEV;
+ }
+#endif
+
/* ignore error return of fb_get_options */
fb_get_options("vesafb", &option);
vesafb_setup(option);
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index df77ba9..a460ee4 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1253,6 +1253,17 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver);
}
/*
+ * Synthetic Video GUID
+ * {DA0A7802-E377-4aac-8E77-0558EB1073F8}
+ */
+#define HV_SYNTHVID_GUID \
+ .guid = { \
+ 0x02, 0x78, 0x0a, 0xda, 0x77, 0xe3, 0xac, 0x4a, \
+ 0x8e, 0x77, 0x05, 0x58, 0xeb, 0x10, 0x73, 0xf8 \
+ }
+
+
+/*
* Common header for Hyper-V ICs
*/
--
1.7.4.1
^ permalink raw reply related
* [RFC] ARM: shmobile: add framebuffer and backlight support to kzm9g-reference
From: Guennadi Liakhovetski @ 2013-02-15 10:36 UTC (permalink / raw)
To: linux-kernel
Cc: Magnus Damm, Simon Horman, devicetree-discuss, Samuel Ortiz,
Mark Brown, Liam Girdwood, Richard Purdie, Andrew Morton,
linux-fbdev
In-Reply-To: <Pine.LNX.4.64.1302151101140.7446@axis700.grange>
This adds support for the framebuffer and an AS3711 PMIC, used for supplying
power to the CPU, some peripherals and the backlight.
not-Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
As is clear from the not-Sob line and the RFC in the subject, this is not
for mainline. This is just an illustration how the recently submitted
"mfd: as3711: add OF support" patch can be used with the kzm9g board. I
had to add the framebuffer support to kzm9g, because without it the
backlight would just be turned on with no image on the LCD, which doesn't
look pretty. In the future, if needed, we could first only apply the
regulator part of the .dts, leaving the backlight disabled.
arch/arm/boot/dts/sh73a0-kzm9g-reference.dts | 90 ++++++++++++++++++++++++
arch/arm/mach-shmobile/board-kzm9g-reference.c | 73 +++++++++++++++++++-
2 files changed, 162 insertions(+), 1 deletions(-)
diff --git a/arch/arm/boot/dts/sh73a0-kzm9g-reference.dts b/arch/arm/boot/dts/sh73a0-kzm9g-reference.dts
index b93b16a..a5810fc 100644
--- a/arch/arm/boot/dts/sh73a0-kzm9g-reference.dts
+++ b/arch/arm/boot/dts/sh73a0-kzm9g-reference.dts
@@ -99,6 +99,12 @@
toshiba,mmc-wrprotect-disable;
toshiba,mmc-has-idle-wait;
};
+
+ /* A dummy LCDC DT node for now */
+ lcdc: lcdc@0xfe940000 {
+ compatible = "renesas,sh-mobile-lcdc";
+ reg = <0xfe940000 0x4000>;
+ };
};
&i2c3 {
@@ -106,6 +112,90 @@
pinctrl-0 = <&i2c3_pins>;
};
+&i2c0 {
+ as3711@40 {
+ compatible = "ams,as3711";
+ reg = <0x40>;
+
+ regulators {
+ sd1 {
+ regulator-name = "1.315V CPU";
+ regulator-min-microvolt = <1315000>;
+ regulator-max-microvolt = <1335000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ sd2 {
+ regulator-name = "1.8V";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ sd4 {
+ regulator-name = "1.215V";
+ regulator-min-microvolt = <1215000>;
+ regulator-max-microvolt = <1235000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ ldo2 {
+ regulator-name = "2.8V CPU";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <2800000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ ldo3 {
+ regulator-name = "3.0V CPU";
+ regulator-min-microvolt = <3000000>;
+ regulator-max-microvolt = <3000000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ ldo4 {
+ regulator-name = "2.8V";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <2800000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ ldo5 {
+ regulator-name = "2.8V #2";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <2800000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ ldo7 {
+ regulator-name = "1.15V CPU";
+ regulator-min-microvolt = <1150000>;
+ regulator-max-microvolt = <1150000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ ldo8 {
+ regulator-name = "1.15V CPU #2";
+ regulator-min-microvolt = <1150000>;
+ regulator-max-microvolt = <1150000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ };
+
+ backlight {
+ compatible = "ams,as3711-bl";
+ su2-dev = <&lcdc>;
+ su2-max-uA = <36000>;
+ su2-feedback-curr-auto;
+ su2-fbprot-gpio4;
+ su2-auto-curr1;
+ su2-auto-curr2;
+ su2-auto-curr3;
+ };
+ };
+};
+
&gpio {
sdhi0_pins: pfc_sdhi0_pins {
renesas,pins = "sdhi0_data4", "sdhi0_ctrl", "sdhi0_wp", "sdhi0_cd";
diff --git a/arch/arm/mach-shmobile/board-kzm9g-reference.c b/arch/arm/mach-shmobile/board-kzm9g-reference.c
index 1490246..9d520ca 100644
--- a/arch/arm/mach-shmobile/board-kzm9g-reference.c
+++ b/arch/arm/mach-shmobile/board-kzm9g-reference.c
@@ -31,6 +31,7 @@
#include <linux/regulator/fixed.h>
#include <linux/regulator/machine.h>
#include <linux/smsc911x.h>
+#include <linux/videodev2.h>
#include <mach/irqs.h>
#include <mach/sh73a0.h>
@@ -39,6 +40,62 @@
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
+#include <video/sh_mobile_lcdc.h>
+
+/* LCDC */
+static struct fb_videomode kzm_lcdc_mode = {
+ .name = "WVGA Panel",
+ .xres = 800,
+ .yres = 480,
+ .left_margin = 220,
+ .right_margin = 110,
+ .hsync_len = 70,
+ .upper_margin = 20,
+ .lower_margin = 5,
+ .vsync_len = 5,
+ .sync = 0,
+};
+
+static struct sh_mobile_lcdc_info lcdc_info = {
+ .clock_source = LCDC_CLK_BUS,
+ .ch[0] = {
+ .chan = LCDC_CHAN_MAINLCD,
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .interface_type = RGB24,
+ .lcd_modes = &kzm_lcdc_mode,
+ .num_modes = 1,
+ .clock_divider = 5,
+ .flags = 0,
+ .panel_cfg = {
+ .width = 152,
+ .height = 91,
+ },
+ }
+};
+
+static struct resource lcdc_resources[] = {
+ [0] = {
+ .name = "LCDC",
+ .start = 0xfe940000,
+ .end = 0xfe943fff,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = intcs_evt2irq(0x580),
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct platform_device lcdc_device = {
+ .name = "sh_mobile_lcdc_fb",
+ .num_resources = ARRAY_SIZE(lcdc_resources),
+ .resource = lcdc_resources,
+ .dev = {
+ .platform_data = &lcdc_info,
+ .coherent_dma_mask = ~0,
+ },
+};
+
/* Dummy supplies, where voltage doesn't matter */
static struct regulator_consumer_supply dummy_supplies[] = {
REGULATOR_SUPPLY("vddvario", "smsc911x"),
@@ -79,6 +136,15 @@ static const struct pinctrl_map kzm_pinctrl_map[] = {
"scifa4_data", "scifa4"),
PIN_MAP_MUX_GROUP_DEFAULT("sh-sci.4", "e6050000.pfc",
"scifa4_ctrl", "scifa4"),
+ PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_lcdc_fb.0", "e6050000.pfc",
+ "lcd_data24", "lcd"),
+ PIN_MAP_MUX_GROUP_DEFAULT("sh_mobile_lcdc_fb.0", "e6050000.pfc",
+ "lcd_sync", "lcd"),
+};
+
+static struct platform_device *kzm_devices[] __initdata = {
+ &smsc_device,
+ &lcdc_device,
};
static void __init kzm_init(void)
@@ -88,7 +154,10 @@ static void __init kzm_init(void)
pinctrl_register_mappings(kzm_pinctrl_map, ARRAY_SIZE(kzm_pinctrl_map));
regulator_register_fixed(2, dummy_supplies, ARRAY_SIZE(dummy_supplies));
- platform_device_register(&smsc_device);
+
+ /* LCDC */
+ gpio_request_one(222, GPIOF_OUT_INIT_HIGH, NULL); /* LCDCDON */
+ gpio_request_one(226, GPIOF_OUT_INIT_HIGH, NULL); /* SC */
/*
* Enable SD: this is a pseudo-GPIO, it actually only sets bit 28 in
@@ -101,6 +170,8 @@ static void __init kzm_init(void)
/* Early BRESP enable, Shared attribute override enable, 64K*8way */
l2x0_init(IOMEM(0xf0100000), 0x40460000, 0x82000fff);
#endif
+
+ platform_add_devices(kzm_devices, ARRAY_SIZE(kzm_devices));
}
static void kzm9g_restart(char mode, const char *cmd)
--
1.7.2.5
^ permalink raw reply related
* [PATCH/RFC] mfd: as3711: add OF support
From: Guennadi Liakhovetski @ 2013-02-15 10:07 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: linux-fbdev-u79uwXL29TY76Z2rM5mHXA, Samuel Ortiz,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ, Mark Brown,
Magnus Damm, Simon Horman, Richard Purdie, Andrew Morton,
Liam Girdwood
Add device-tree bindings to the AS3711 regulator and backlight drivers.
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
As usual - comments to the new bindings are very welcome!
Documentation/devicetree/bindings/mfd/as3711.txt | 73 +++++++++++++
drivers/mfd/as3711.c | 30 +++++-
drivers/regulator/as3711-regulator.c | 69 ++++++++++++-
drivers/video/backlight/as3711_bl.c | 118 +++++++++++++++++++++-
4 files changed, 282 insertions(+), 8 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mfd/as3711.txt
diff --git a/Documentation/devicetree/bindings/mfd/as3711.txt b/Documentation/devicetree/bindings/mfd/as3711.txt
new file mode 100644
index 0000000..d98cf18
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/as3711.txt
@@ -0,0 +1,73 @@
+AS3711 is an I2C PMIC from Austria MicroSystems with multiple DCDC and LDO power
+supplies, a battery charger and an RTC. So far only bindings for the two stepup
+DCDC converters are defined. Other DCDC and LDO supplies are configured, using
+standard regulator properties, they must belong to a sub-node, called
+"regulators" and be called "sd1" to "sd4" and "ldo1" to "ldo8." Stepup converter
+configuration should be placed in a subnode, called "backlight."
+
+Compulsory properties:
+- compatible : must be "ams,as3711"
+- reg : specifies the I2C address
+
+To use the SU1 converter as a backlight source the following two properties must
+be provided:
+- su1-dev : framebuffer phandle
+- su1-max-uA : maximum current
+
+To use the SU2 converter as a backlight source the following two properties must
+be provided:
+- su2-dev : framebuffer phandle
+- su1-max-uA : maximum current
+
+Additionally one of these properties must be provided to select the type of
+feedback used:
+- su2-feedback-voltage : voltage feedback is used
+- su2-feedback-curr1 : CURR1 input used for current feedback
+- su2-feedback-curr2 : CURR2 input used for current feedback
+- su2-feedback-curr3 : CURR3 input used for current feedback
+- su2-feedback-curr-auto: automatic current feedback selection
+
+and one of these to select the over-voltage protection pin
+- su2-fbprot-lx-sd4 : LX_SD4 is used for over-voltage protection
+- su2-fbprot-gpio2 : GPIO2 is used for over-voltage protection
+- su2-fbprot-gpio3 : GPIO3 is used for over-voltage protection
+- su2-fbprot-gpio4 : GPIO4 is used for over-voltage protection
+
+If "su2-feedback-curr-auto" is selected, one or more of the following properties
+have to be specified:
+- su2-auto-curr1 : use CURR1 input for current feedback
+- su2-auto-curr2 : use CURR2 input for current feedback
+- su2-auto-curr3 : use CURR3 input for current feedback
+
+Example:
+
+as3711@40 {
+ compatible = "ams,as3711";
+ reg = <0x40>;
+
+ regulators {
+ sd4 {
+ regulator-name = "1.215V";
+ regulator-min-microvolt = <1215000>;
+ regulator-max-microvolt = <1235000>;
+ };
+ ldo2 {
+ regulator-name = "2.8V CPU";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <2800000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ };
+
+ backlight {
+ compatible = "ams,as3711-bl";
+ su2-dev = <&lcdc>;
+ su2-max-uA = <36000>;
+ su2-feedback-curr-auto;
+ su2-fbprot-gpio4;
+ su2-auto-curr1;
+ su2-auto-curr2;
+ su2-auto-curr3;
+ };
+};
diff --git a/drivers/mfd/as3711.c b/drivers/mfd/as3711.c
index e994c96..5e0e8b3 100644
--- a/drivers/mfd/as3711.c
+++ b/drivers/mfd/as3711.c
@@ -112,16 +112,37 @@ static const struct regmap_config as3711_regmap_config = {
.cache_type = REGCACHE_RBTREE,
};
+#ifdef CONFIG_OF
+static struct of_device_id as3711_of_match[] = {
+ {.compatible = "ams,as3711",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, as3711_of_match);
+#endif
+
static int as3711_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct as3711 *as3711;
- struct as3711_platform_data *pdata = client->dev.platform_data;
+ struct as3711_platform_data *pdata;
unsigned int id1, id2;
int ret;
- if (!pdata)
- dev_dbg(&client->dev, "Platform data not found\n");
+ if (!client->dev.of_node) {
+ pdata = client->dev.platform_data;
+ if (!pdata)
+ dev_dbg(&client->dev, "Platform data not found\n");
+ } else {
+ if (!of_device_is_available(client->dev.of_node))
+ return -ENODEV;
+
+ pdata = devm_kzalloc(&client->dev,
+ sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&client->dev, "Failed to allocate pdata\n");
+ return -ENOMEM;
+ }
+ }
as3711 = devm_kzalloc(&client->dev, sizeof(struct as3711), GFP_KERNEL);
if (!as3711) {
@@ -193,7 +214,8 @@ static struct i2c_driver as3711_i2c_driver = {
.driver = {
.name = "as3711",
.owner = THIS_MODULE,
- },
+ .of_match_table = of_match_ptr(as3711_of_match),
+ },
.probe = as3711_i2c_probe,
.remove = as3711_i2c_remove,
.id_table = as3711_i2c_id,
diff --git a/drivers/regulator/as3711-regulator.c b/drivers/regulator/as3711-regulator.c
index f0ba8c4..cf145fc 100644
--- a/drivers/regulator/as3711-regulator.c
+++ b/drivers/regulator/as3711-regulator.c
@@ -13,9 +13,11 @@
#include <linux/init.h>
#include <linux/mfd/as3711.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
#include <linux/slab.h>
struct as3711_regulator_info {
@@ -276,6 +278,57 @@ static struct as3711_regulator_info as3711_reg_info[] = {
#define AS3711_REGULATOR_NUM ARRAY_SIZE(as3711_reg_info)
+static const char *as3711_regulator_of_names[AS3711_REGULATOR_NUM] = {
+ [AS3711_REGULATOR_SD_1] = "sd1",
+ [AS3711_REGULATOR_SD_2] = "sd2",
+ [AS3711_REGULATOR_SD_3] = "sd3",
+ [AS3711_REGULATOR_SD_4] = "sd4",
+ [AS3711_REGULATOR_LDO_1] = "ldo1",
+ [AS3711_REGULATOR_LDO_2] = "ldo2",
+ [AS3711_REGULATOR_LDO_3] = "ldo3",
+ [AS3711_REGULATOR_LDO_4] = "ldo4",
+ [AS3711_REGULATOR_LDO_5] = "ldo5",
+ [AS3711_REGULATOR_LDO_6] = "ldo6",
+ [AS3711_REGULATOR_LDO_7] = "ldo7",
+ [AS3711_REGULATOR_LDO_8] = "ldo8",
+};
+
+static int as3711_regulator_parse_dt(struct device *dev)
+{
+ struct as3711_regulator_pdata *pdata = dev_get_platdata(dev);
+ struct device_node *regulators + of_find_node_by_name(dev->parent->of_node, "regulators");
+ struct of_regulator_match *matches, *match;
+ const int count = AS3711_REGULATOR_NUM;
+ int ret, i;
+
+ if (!regulators) {
+ dev_err(dev, "regulator node not found\n");
+ return -ENODEV;
+ }
+
+ matches = devm_kzalloc(dev, sizeof(*matches) * count, GFP_KERNEL);
+ if (!matches)
+ return -ENOMEM;
+
+ for (i = 0, match = matches; i < count; i++, match++) {
+ match->name = as3711_regulator_of_names[i];
+ match->driver_data = as3711_reg_info + i;
+ }
+
+ ret = of_regulator_match(dev->parent, regulators, matches, count);
+ if (ret < 0) {
+ dev_err(dev, "Error parsing regulator init data: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0, match = matches; i < count; i++, match++)
+ if (match->of_node)
+ pdata->init_data[i] = match->init_data;
+
+ return 0;
+}
+
static int as3711_regulator_probe(struct platform_device *pdev)
{
struct as3711_regulator_pdata *pdata = dev_get_platdata(&pdev->dev);
@@ -289,8 +342,18 @@ static int as3711_regulator_probe(struct platform_device *pdev)
int ret;
int id;
- if (!pdata)
- dev_dbg(&pdev->dev, "No platform data...\n");
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data...\n");
+ return -ENODEV;
+ }
+
+ if (pdev->dev.parent->of_node) {
+ ret = as3711_regulator_parse_dt(&pdev->dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
+ return ret;
+ }
+ }
regs = devm_kzalloc(&pdev->dev, AS3711_REGULATOR_NUM *
sizeof(struct as3711_regulator), GFP_KERNEL);
@@ -300,7 +363,7 @@ static int as3711_regulator_probe(struct platform_device *pdev)
}
for (id = 0, ri = as3711_reg_info; id < AS3711_REGULATOR_NUM; ++id, ri++) {
- reg_data = pdata ? pdata->init_data[id] : NULL;
+ reg_data = pdata->init_data[id];
/* No need to register if there is no regulator data */
if (!reg_data)
diff --git a/drivers/video/backlight/as3711_bl.c b/drivers/video/backlight/as3711_bl.c
index c6bc65d..90c9208 100644
--- a/drivers/video/backlight/as3711_bl.c
+++ b/drivers/video/backlight/as3711_bl.c
@@ -257,6 +257,109 @@ static int as3711_bl_register(struct platform_device *pdev,
return 0;
}
+static int as3711_backlight_parse_dt(struct device *dev)
+{
+ struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
+ struct device_node *bl + of_find_node_by_name(dev->parent->of_node, "backlight"), *fb;
+ int ret;
+
+ if (!bl) {
+ dev_dbg(dev, "backlight node not found\n");
+ return -ENODEV;
+ }
+
+ fb = of_parse_phandle(bl, "su1-dev", 0);
+ if (fb) {
+ pdata->su1_fb = fb->full_name;
+
+ ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
+ if (pdata->su1_max_uA <= 0)
+ ret = -EINVAL;
+ if (ret < 0)
+ return ret;
+ }
+
+ fb = of_parse_phandle(bl, "su2-dev", 0);
+ if (fb) {
+ int count = 0;
+
+ pdata->su2_fb = fb->full_name;
+
+ ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA);
+ if (pdata->su2_max_uA <= 0)
+ ret = -EINVAL;
+ if (ret < 0)
+ return ret;
+
+ if (of_find_property(bl, "su2-feedback-voltage", NULL)) {
+ pdata->su2_feedback = AS3711_SU2_VOLTAGE;
+ count++;
+ }
+ if (of_find_property(bl, "su2-feedback-curr1", NULL)) {
+ pdata->su2_feedback = AS3711_SU2_CURR1;
+ count++;
+ }
+ if (of_find_property(bl, "su2-feedback-curr2", NULL)) {
+ pdata->su2_feedback = AS3711_SU2_CURR2;
+ count++;
+ }
+ if (of_find_property(bl, "su2-feedback-curr3", NULL)) {
+ pdata->su2_feedback = AS3711_SU2_CURR3;
+ count++;
+ }
+ if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) {
+ pdata->su2_feedback = AS3711_SU2_CURR_AUTO;
+ count++;
+ }
+ if (count != 1)
+ return -EINVAL;
+
+ count = 0;
+ if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) {
+ pdata->su2_fbprot = AS3711_SU2_LX_SD4;
+ count++;
+ }
+ if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) {
+ pdata->su2_fbprot = AS3711_SU2_GPIO2;
+ count++;
+ }
+ if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) {
+ pdata->su2_fbprot = AS3711_SU2_GPIO3;
+ count++;
+ }
+ if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) {
+ pdata->su2_fbprot = AS3711_SU2_GPIO4;
+ count++;
+ }
+ if (count != 1)
+ return -EINVAL;
+
+ count = 0;
+ if (of_find_property(bl, "su2-auto-curr1", NULL)) {
+ pdata->su2_auto_curr1 = true;
+ count++;
+ }
+ if (of_find_property(bl, "su2-auto-curr2", NULL)) {
+ pdata->su2_auto_curr2 = true;
+ count++;
+ }
+ if (of_find_property(bl, "su2-auto-curr3", NULL)) {
+ pdata->su2_auto_curr3 = true;
+ count++;
+ }
+
+ /*
+ * At least one su2-auto-curr* must be specified iff
+ * AS3711_SU2_CURR_AUTO is used
+ */
+ if (!count ^ pdata->su2_feedback != AS3711_SU2_CURR_AUTO)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int as3711_backlight_probe(struct platform_device *pdev)
{
struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
@@ -266,11 +369,24 @@ static int as3711_backlight_probe(struct platform_device *pdev)
unsigned int max_brightness;
int ret;
- if (!pdata || (!pdata->su1_fb && !pdata->su2_fb)) {
+ if (!pdata) {
dev_err(&pdev->dev, "No platform data, exiting...\n");
return -ENODEV;
}
+ if (pdev->dev.parent->of_node) {
+ ret = as3711_backlight_parse_dt(&pdev->dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (!pdata->su1_fb && !pdata->su2_fb) {
+ dev_err(&pdev->dev, "No framebuffer specified\n");
+ return -EINVAL;
+ }
+
/*
* Due to possible hardware damage I chose to block all modes,
* unsupported on my hardware. Anyone, wishing to use any of those modes
--
1.7.2.5
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox