* Re: [RFC 1/4] video: panel: add CLAA101WA01A panel support
From: Alexandre Courbot @ 2013-01-31 4:24 UTC (permalink / raw)
To: Mark Zhang
Cc: Stephen Warren, Laurent Pinchart, Thierry Reding, Mark Zhang,
Linux Kernel Mailing List,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-tegra-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Alexandre Courbot
In-Reply-To: <5109EA2A.8020204-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On Thu, Jan 31, 2013 at 12:51 PM, Mark Zhang <nvmarkzhang@gmail.com> wrote:
>> DDC access is a property of the display controller, not the panel
>> itself. The panel might be hooked up to a display controller's DDC/I2C
>> channel as the target, but it isn't the host/controller of the DDC/I2C
>> channel. As such, placing the nvidia,ddc property into the display
>> controller node makes sense.
>>
>
> Yes, DC triggers the DDC access and is the host of the DDC/I2C channel.
> So I think it's reasonable to put nvidia,ddc property into the display
> controller node. But the video mode info in EDID which be fetched via
> DDC is the property of the panel, so this info should be provided by
> panel driver. Actually display controller cares about the video modes,
> not the way to get these info. So I think it's better to do the whole
> work like this:
>
> 1) display controller relied on CDF to call "display_entity_get_modes"
> 2) panel driver calls the function which is hooked to display controller
> to get the video modes via DDC.
> 3) if the video modes can't be fetched via DDC, panel driver provides
> the info(either by hard-coded or defined in DT) anyway
This would require a callback dedicated to this in display_entity and
would break its simple design.
> Just like I said above, display controller should not query DDC
> directly. Since CDF already defines these for us, display controller
> should call CDF's functions, like: display_entity_get_modes,
> display_entity_get_size... I think this is the key difference I wanna
> talk about. And also in this way, display controller doesn't need to
> care about this 2 steps logics, just call "display_entity_get_modes" is OK.
Well the fact is that it *is* the display controller that queries DDC
directly. The panel is just a bus here, and the EDID EEPROM could
perfectly work without it. If you model what you described with DT
nodes, I'm afraid you will end up with something both complex (with DC
node referencing panel node and back again for the EDID bus) and that
does not picture reality accurately.
Alex.
^ permalink raw reply
* Re: [RFC 1/4] video: panel: add CLAA101WA01A panel support
From: Alexandre Courbot @ 2013-01-31 4:14 UTC (permalink / raw)
To: Stephen Warren
Cc: Laurent Pinchart, Thierry Reding, Mark Zhang,
Linux Kernel Mailing List,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-tegra-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Alexandre Courbot
In-Reply-To: <51098229.7080508-3lzwWm7+Weoh9ZMKESR00Q@public.gmane.org>
On Thu, Jan 31, 2013 at 5:27 AM, Stephen Warren <swarren@wwwdotorg.org> wrote:
> So this looks like a reasonable binding to me. The one issue is that
> it's very generic, and if we go this route, we'll probably end up with
> tens or hundreds of identical or extremely similar simple bindings, and
> associated drivers.
>
> We can avoid this instead by defining something like a "simple-lcd"
> binding, and associated driver, that will work for perhaps 90%, 95%,
> 99%, even 100%(?) of panels.
That seems totally doable indeed. Actually the right way to do this
might be by extending the simple DPI panel driver Laurent included in
his patchset.
> Just like the above, but with:
>
> compatible="simple-panel", "chunghwa,claa101wa01a"
>
> instead, and the driver binding to "simple-panel" rather than
> "chunghwa,claa101wa01a".
Just out of curiosity, why don't we rather define
compatible="chunghwa,claa101wa01a", "simple-panel"
in that order? I thought DT compatible strings should go from more to
less specific. The device would still bind to "simple-panel" if no
more specific driver exists.
> The driver can assume that a specific set of supplies (and perhaps
> GPIOs) is always present, and that the /sequence/ of manipulating those
> is fixed. This will avoid the need for anything like the power sequences
> code. If a particular panel doesn't fit those assumptions, including the
> exact sequence of manipulations for each state transition (which should
> be documented in the binding) then it can get a custom driver, this also
> avoiding having to define custom sequences in DT.
>
> Things that might be parameterized/optional:
>
> * Perhaps some GPIOs aren't always present.
> * If some regulators aren't SW-controllable, DT should still provide a
> fixed/dummy regulator so the driver doesn't care.
How about making all regulators and GPIO optional in the driver?
> * Wait times between regulator/GPIO/... manipulation could be specified
> in DT.
> * For panels without EDID, CDF DT bindings can provide the list of
> supported modes, otherwise we assume that the display controller that
> drives the panel has been told how to access the EDID, just like it
> would for an "external" display.
Excellent. Thanks for the feedback.
Alex.
^ permalink raw reply
* Re: [RFC 1/4] video: panel: add CLAA101WA01A panel support
From: Mark Zhang @ 2013-01-31 3:51 UTC (permalink / raw)
To: Stephen Warren
Cc: Alexandre Courbot, Laurent Pinchart, Thierry Reding, Mark Zhang,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
linux-tegra-u79uwXL29TY76Z2rM5mHXA, gnurou-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <51098064.7030902-3lzwWm7+Weoh9ZMKESR00Q@public.gmane.org>
On 01/31/2013 04:19 AM, Stephen Warren wrote:
> On 01/30/2013 12:20 AM, Mark Zhang wrote:
>> On 01/30/2013 11:02 AM, Alexandre Courbot wrote:
>>> Add support for the Chunghwa CLAA101WA01A display panel.
>
>>> +static int panel_claa101_get_modes(struct display_entity *entity,
>>> + const struct videomode **modes)
>>> +{
>>> + /* TODO get modes from EDID? */
>>
>> Why not move the "nvidia,ddc" from encoder's DT to panel's DT? In that
>> case, you can get EDID here. I know drm has some helpers to fetch EDID
>> but I recall there are some other functions which has no drm
>> dependencies which may be suitable for you.
>
> DDC access is a property of the display controller, not the panel
> itself. The panel might be hooked up to a display controller's DDC/I2C
> channel as the target, but it isn't the host/controller of the DDC/I2C
> channel. As such, placing the nvidia,ddc property into the display
> controller node makes sense.
>
Yes, DC triggers the DDC access and is the host of the DDC/I2C channel.
So I think it's reasonable to put nvidia,ddc property into the display
controller node. But the video mode info in EDID which be fetched via
DDC is the property of the panel, so this info should be provided by
panel driver. Actually display controller cares about the video modes,
not the way to get these info. So I think it's better to do the whole
work like this:
1) display controller relied on CDF to call "display_entity_get_modes"
2) panel driver calls the function which is hooked to display controller
to get the video modes via DDC.
3) if the video modes can't be fetched via DDC, panel driver provides
the info(either by hard-coded or defined in DT) anyway
> Re: the other discussion in this thread: Probably the simplest is if
> tegradrm/other-CDF-users do something like:
>
> 1) If DDC I2C channel available for this display channel, query DDC.
Just like I said above, display controller should not query DDC
directly. Since CDF already defines these for us, display controller
should call CDF's functions, like: display_entity_get_modes,
display_entity_get_size... I think this is the key difference I wanna
talk about. And also in this way, display controller doesn't need to
care about this 2 steps logics, just call "display_entity_get_modes" is OK.
Mark
> 2) If not, or perhaps also if that fails, query the panel driver for the
> mode list.
>
> That would cover all bases very simply.
>
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Linus Torvalds @ 2013-01-31 0:26 UTC (permalink / raw)
To: Russell King
Cc: Dave Airlie, Daniel Vetter, Andrew Morton,
Florian Tobias Schandinat, linux-fbdev@vger.kernel.org,
Linux Kernel Mailing List, Greg Kroah-Hartman, dri-devel
In-Reply-To: <20130131001339.GC14801@flint.arm.linux.org.uk>
On Thu, Jan 31, 2013 at 11:13 AM, Russell King <rmk@arm.linux.org.uk> wrote:
>
> Which may or may not be a good thing depending how you look at it; it
> means that once your kernel blanks, you get a lockdep dump. At that
> point you lose lockdep checking for everything else because lockdep
> disables itself after the first dump.
Fair enough, we may want to revert the lockdep checking for
console_lock, and make re-enabling it part of the patch-series that
fixes the locking.
Daniel/Dave? Does that sound reasonable?
Linus
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Russell King @ 2013-01-31 0:13 UTC (permalink / raw)
To: Dave Airlie
Cc: Linus Torvalds, Daniel Vetter, Andrew Morton,
Florian Tobias Schandinat, linux-fbdev@vger.kernel.org,
Linux Kernel Mailing List, Greg Kroah-Hartman, dri-devel
In-Reply-To: <CAPM=9tz6YBh2o3T8Ogt4K=wxSrC3Jc4PCJMzfpbyY=B132fA5g@mail.gmail.com>
On Thu, Jan 31, 2013 at 10:04:05AM +1000, Dave Airlie wrote:
> On Thu, Jan 31, 2013 at 9:52 AM, Linus Torvalds
> <torvalds@linux-foundation.org> wrote:
> > On Thu, Jan 31, 2013 at 9:19 AM, Russell King <rmk@arm.linux.org.uk> wrote:
> >>
> >> So... what you seem to be telling me is that 3.9 is going to be a
> >> release which issues lockdep complaints when the console blanks, and
> >> you think that's acceptable?
> >>
> >> Adding Linus and Andrew so they're aware of this issue...
> >
> > Oh, we're extremely aware of it. And it's not a new issue, the locking
> > problem have apparently been around forever, although I'm not sure why
> > the lockdep splat itself started happening only recently.
> >
> > They'll make it into 3.9, it's 3.8 that won't have them. The patches
> > initially caused way *worse* behavior than just a lockdep splat - they
> > caused actual hard lockups (and that was *after* the initial series of
> > fixes). That got fixed (hopefully for the last case!) fairly recently,
> > and I'm not willing to take the scary patch-series that has had
> > several problem cases.
>
> Well we didn't have any lock validation support before Daniel added it
> a couple of kernels back,
> so instead of hidden locking problems we've had from time began, we now have
> lockdep detectable locking problems.
Which may or may not be a good thing depending how you look at it; it
means that once your kernel blanks, you get a lockdep dump. At that
point you lose lockdep checking for everything else because lockdep
disables itself after the first dump.
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of:
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Russell King @ 2013-01-31 0:09 UTC (permalink / raw)
To: Linus Torvalds
Cc: Daniel Vetter, Andrew Morton, Florian Tobias Schandinat,
linux-fbdev@vger.kernel.org, Linux Kernel Mailing List,
Greg Kroah-Hartman, dri-devel, Dave Airlie
In-Reply-To: <CA+55aFyRHK3dmwQf_mdfB18dmoaRZqBG9FvHRqTs7cqBjgUs+g@mail.gmail.com>
On Thu, Jan 31, 2013 at 10:52:51AM +1100, Linus Torvalds wrote:
> On Thu, Jan 31, 2013 at 9:19 AM, Russell King <rmk@arm.linux.org.uk> wrote:
> >
> > So... what you seem to be telling me is that 3.9 is going to be a
> > release which issues lockdep complaints when the console blanks, and
> > you think that's acceptable?
> >
> > Adding Linus and Andrew so they're aware of this issue...
>
> Oh, we're extremely aware of it. And it's not a new issue, the locking
> problem have apparently been around forever, although I'm not sure why
> the lockdep splat itself started happening only recently.
Well, the reason the splat started happening recently is because of:
commit daee779718a319ff9f83e1ba3339334ac650bb22
Author: Daniel Vetter <daniel.vetter@ffwll.ch>
Date: Sat Sep 22 19:52:11 2012 +0200
console: implement lockdep support for console_lock
Dave Airlie recently discovered a locking bug in the fbcon layer,
where a timer_del_sync (for the blinking cursor) deadlocks with the
timer itself, since both (want to) hold the console_lock:
https://lkml.org/lkml/2012/8/21/36
which, if I'm looking at the git history right, appears to have come
in during the last merge window?
Yes, the locking may be wrong, but we've lived with that locking for
a long time without problem.
Can we at least silence these warnings by temporarily disabling the
lockdep tracking added by the above commit for this lock, until the
fixes for this are merged during the next merge window?
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of:
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Dave Airlie @ 2013-01-31 0:04 UTC (permalink / raw)
To: Linus Torvalds
Cc: Russell King, Daniel Vetter, Andrew Morton,
Florian Tobias Schandinat, linux-fbdev@vger.kernel.org,
Linux Kernel Mailing List, Greg Kroah-Hartman, dri-devel
In-Reply-To: <CA+55aFyRHK3dmwQf_mdfB18dmoaRZqBG9FvHRqTs7cqBjgUs+g@mail.gmail.com>
On Thu, Jan 31, 2013 at 9:52 AM, Linus Torvalds
<torvalds@linux-foundation.org> wrote:
> On Thu, Jan 31, 2013 at 9:19 AM, Russell King <rmk@arm.linux.org.uk> wrote:
>>
>> So... what you seem to be telling me is that 3.9 is going to be a
>> release which issues lockdep complaints when the console blanks, and
>> you think that's acceptable?
>>
>> Adding Linus and Andrew so they're aware of this issue...
>
> Oh, we're extremely aware of it. And it's not a new issue, the locking
> problem have apparently been around forever, although I'm not sure why
> the lockdep splat itself started happening only recently.
>
> They'll make it into 3.9, it's 3.8 that won't have them. The patches
> initially caused way *worse* behavior than just a lockdep splat - they
> caused actual hard lockups (and that was *after* the initial series of
> fixes). That got fixed (hopefully for the last case!) fairly recently,
> and I'm not willing to take the scary patch-series that has had
> several problem cases.
Well we didn't have any lock validation support before Daniel added it
a couple of kernels back,
so instead of hidden locking problems we've had from time began, we now have
lockdep detectable locking problems.
Dave.
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Linus Torvalds @ 2013-01-30 23:52 UTC (permalink / raw)
To: Russell King
Cc: Daniel Vetter, Andrew Morton, Florian Tobias Schandinat,
linux-fbdev@vger.kernel.org, Linux Kernel Mailing List,
Greg Kroah-Hartman, dri-devel, Dave Airlie
In-Reply-To: <20130130221946.GA14801@flint.arm.linux.org.uk>
On Thu, Jan 31, 2013 at 9:19 AM, Russell King <rmk@arm.linux.org.uk> wrote:
>
> So... what you seem to be telling me is that 3.9 is going to be a
> release which issues lockdep complaints when the console blanks, and
> you think that's acceptable?
>
> Adding Linus and Andrew so they're aware of this issue...
Oh, we're extremely aware of it. And it's not a new issue, the locking
problem have apparently been around forever, although I'm not sure why
the lockdep splat itself started happening only recently.
They'll make it into 3.9, it's 3.8 that won't have them. The patches
initially caused way *worse* behavior than just a lockdep splat - they
caused actual hard lockups (and that was *after* the initial series of
fixes). That got fixed (hopefully for the last case!) fairly recently,
and I'm not willing to take the scary patch-series that has had
several problem cases.
LInus
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Daniel Vetter @ 2013-01-30 22:27 UTC (permalink / raw)
To: Russell King
Cc: Linus Torvalds, Andrew Morton, Florian Tobias Schandinat,
linux-fbdev, linux-kernel, Greg Kroah-Hartman, dri-devel,
Dave Airlie
In-Reply-To: <20130130221946.GA14801@flint.arm.linux.org.uk>
On Wed, Jan 30, 2013 at 11:19 PM, Russell King <rmk@arm.linux.org.uk> wrote:
> So... what you seem to be telling me is that 3.9 is going to be a
> release which issues lockdep complaints when the console blanks, and
> you think that's acceptable?
>
> Adding Linus and Andrew so they're aware of this issue...
Linus was the guy who shot down the patches for 3.9 since one of the
earlier iterations caused instant deadlocks - I've been pushing them
to merge them ;-)
-Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Russell King @ 2013-01-30 22:19 UTC (permalink / raw)
To: Daniel Vetter, Linus Torvalds
Cc: Florian Tobias Schandinat, linux-fbdev, linux-kernel, akpm,
Greg Kroah-Hartman, dri-devel, Dave Airlie
In-Reply-To: <CAKMK7uHEggaGfLTq4c3MfY7csQSFpb=kRwdbJUQMN9Cz9K=V4A@mail.gmail.com>
On Wed, Jan 30, 2013 at 11:07:16PM +0100, Daniel Vetter wrote:
> On Wed, Jan 30, 2013 at 10:52 PM, Russell King <rmk@arm.linux.org.uk> wrote:
> > Also adding Greg and Daniel to this as Daniel introduced the lockdep
> > checking.
> >
> > This looks extremely horrid to be to solve - the paths are rather deep
> > where the dependency occurs. The two paths between the locks are:
> >
> > console_lock+0x5c/0x70
> > register_con_driver+0x44/0x150
> > take_over_console+0x24/0x3b4
> > fbcon_takeover+0x70/0xd4
> > fbcon_event_notify+0x7c8/0x818
> > notifier_call_chain+0x4c/0x8c
> > __blocking_notifier_call_chain+0x50/0x68
> > blocking_notifier_call_chain+0x20/0x28
> >
> > and
> >
> > __blocking_notifier_call_chain+0x34/0x68
> > blocking_notifier_call_chain+0x20/0x28
> > fb_notifier_call_chain+0x20/0x28
> > fb_blank+0x40/0xac
> > fbcon_blank+0x1f4/0x29c
> > do_blank_screen+0x1b8/0x270
> > console_callback+0x74/0x138
>
> You want Dave Airlie's pile of locking reworks, which fixes all
> currently known offenders around console_lock and fb_notifier. Patches
> won't go into 3.9 since it took a few rounds until they did not cause
> regression by making these deadlocks easier to hit.
>
> http://cgit.freedesktop.org/~airlied/linux/log/?hûcon-locking-fixes
>
> Long term solution would be to abolish the fb_notifier, at least for
> the purpose of linking fbdevs up with the fbcon and just replace those
> with direct function calls. But that requires that we no longer allow
> fbdev drivers and the fbcon to be loaded in any arbitrary order. Or
> just force fbcon to be built-in if enabled, imo the sane choice (no
> one's bothering with config_vt=m either, after all).
So... what you seem to be telling me is that 3.9 is going to be a
release which issues lockdep complaints when the console blanks, and
you think that's acceptable?
Adding Linus and Andrew so they're aware of this issue...
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of:
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Daniel Vetter @ 2013-01-30 22:07 UTC (permalink / raw)
To: Russell King
Cc: Florian Tobias Schandinat, linux-fbdev, linux-kernel, akpm,
Greg Kroah-Hartman, dri-devel, Dave Airlie
In-Reply-To: <20130130215211.GA28418@flint.arm.linux.org.uk>
On Wed, Jan 30, 2013 at 10:52 PM, Russell King <rmk@arm.linux.org.uk> wrote:
> Also adding Greg and Daniel to this as Daniel introduced the lockdep
> checking.
>
> This looks extremely horrid to be to solve - the paths are rather deep
> where the dependency occurs. The two paths between the locks are:
>
> console_lock+0x5c/0x70
> register_con_driver+0x44/0x150
> take_over_console+0x24/0x3b4
> fbcon_takeover+0x70/0xd4
> fbcon_event_notify+0x7c8/0x818
> notifier_call_chain+0x4c/0x8c
> __blocking_notifier_call_chain+0x50/0x68
> blocking_notifier_call_chain+0x20/0x28
>
> and
>
> __blocking_notifier_call_chain+0x34/0x68
> blocking_notifier_call_chain+0x20/0x28
> fb_notifier_call_chain+0x20/0x28
> fb_blank+0x40/0xac
> fbcon_blank+0x1f4/0x29c
> do_blank_screen+0x1b8/0x270
> console_callback+0x74/0x138
You want Dave Airlie's pile of locking reworks, which fixes all
currently known offenders around console_lock and fb_notifier. Patches
won't go into 3.9 since it took a few rounds until they did not cause
regression by making these deadlocks easier to hit.
http://cgit.freedesktop.org/~airlied/linux/log/?hûcon-locking-fixes
Long term solution would be to abolish the fb_notifier, at least for
the purpose of linking fbdevs up with the fbcon and just replace those
with direct function calls. But that requires that we no longer allow
fbdev drivers and the fbcon to be loaded in any arbitrary order. Or
just force fbcon to be built-in if enabled, imo the sane choice (no
one's bothering with config_vt=m either, after all).
Cheers, Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
^ permalink raw reply
* Re: BUG: circular locking dependency detected
From: Russell King @ 2013-01-30 21:52 UTC (permalink / raw)
To: Florian Tobias Schandinat, linux-fbdev, linux-kernel, akpm,
Daniel Vetter, Greg Kroah-Hartman
In-Reply-To: <20130130200648.GA13298@flint.arm.linux.org.uk>
Also adding Greg and Daniel to this as Daniel introduced the lockdep
checking.
This looks extremely horrid to be to solve - the paths are rather deep
where the dependency occurs. The two paths between the locks are:
console_lock+0x5c/0x70
register_con_driver+0x44/0x150
take_over_console+0x24/0x3b4
fbcon_takeover+0x70/0xd4
fbcon_event_notify+0x7c8/0x818
notifier_call_chain+0x4c/0x8c
__blocking_notifier_call_chain+0x50/0x68
blocking_notifier_call_chain+0x20/0x28
and
__blocking_notifier_call_chain+0x34/0x68
blocking_notifier_call_chain+0x20/0x28
fb_notifier_call_chain+0x20/0x28
fb_blank+0x40/0xac
fbcon_blank+0x1f4/0x29c
do_blank_screen+0x1b8/0x270
console_callback+0x74/0x138
On Wed, Jan 30, 2013 at 08:06:48PM +0000, Russell King wrote:
> This looks like a bug in the framebuffer/console layers. Looks like
> we have one path where we call the notifier list, and a called
> function takes the console lock, and another path where we hold the
> console lock while calling the notifier list.
>
> ===========================
> [ INFO: possible circular locking dependency detected ]
> 3.8.0-rc4+ #656 Not tainted
> -------------------------------------------------------
> kworker/0:1/442 is trying to acquire lock:
> ((fb_notifier_list).rwsem){.+.+.+}, at: [<c004ea48>] __blocking_notifier_call_chain+0x34/0x68
>
> but task is already holding lock:
> (console_lock){+.+.+.}, at: [<c01c2b48>] console_callback+0x14/0x138
>
> which lock already depends on the new lock.
>
>
> the existing dependency chain (in reverse order) is:
>
> -> #1 (console_lock){+.+.+.}:
> [<c006f36c>] __lock_acquire+0x1d20/0x1e80
> [<c006fa04>] lock_acquire+0x68/0x7c
> [<c002a894>] console_lock+0x5c/0x70
> [<c01c0adc>] register_con_driver+0x44/0x150
> [<c01c1158>] take_over_console+0x24/0x3b4
> [<c019d778>] fbcon_takeover+0x70/0xd4
> [<c01a3108>] fbcon_event_notify+0x7c8/0x818
> [<c004e538>] notifier_call_chain+0x4c/0x8c
> [<c004ea64>] __blocking_notifier_call_chain+0x50/0x68
> [<c004ea9c>] blocking_notifier_call_chain+0x20/0x28
> [<c0196e70>] fb_notifier_call_chain+0x20/0x28
> [<c0198194>] register_framebuffer+0x18c/0x238
> [<c01a6148>] clcdfb_probe+0x2b0/0x3c0
> [<c01a6da4>] amba_probe+0x88/0xa0
> [<c01d1730>] driver_probe_device+0x84/0x218
> [<c01d1960>] __driver_attach+0x9c/0xa0
> [<c01cfe5c>] bus_for_each_dev+0x5c/0x88
> [<c01d1398>] driver_attach+0x20/0x28
> [<c01d0ddc>] bus_add_driver+0xa4/0x244
> [<c01d1ed4>] driver_register+0x80/0x14c
> [<c01a6848>] amba_driver_register+0x48/0x5c
> [<c043aae0>] amba_clcdfb_init+0x28/0x3c
> [<c000868c>] do_one_initcall+0x44/0x1ac
> [<c0425928>] kernel_init_freeable+0x104/0x1c8
> [<c03242ec>] kernel_init+0x10/0xec
> [<c0014510>] ret_from_fork+0x14/0x24
>
> -> #0 ((fb_notifier_list).rwsem){.+.+.+}:
> [<c006bc44>] print_circular_bug+0x84/0x2f0
> [<c006f458>] __lock_acquire+0x1e0c/0x1e80
> [<c006fa04>] lock_acquire+0x68/0x7c
> [<c032b3a0>] down_read+0x34/0x44
> [<c004ea48>] __blocking_notifier_call_chain+0x34/0x68
> [<c004ea9c>] blocking_notifier_call_chain+0x20/0x28
> [<c0196e70>] fb_notifier_call_chain+0x20/0x28
> [<c0197690>] fb_blank+0x40/0xac
> [<c019f874>] fbcon_blank+0x1f4/0x29c
> [<c01c09e0>] do_blank_screen+0x1b8/0x270
> [<c01c2ba8>] console_callback+0x74/0x138
> [<c00408c8>] process_one_work+0x1b4/0x4ec
> [<c0043610>] worker_thread+0x17c/0x4bc
> [<c004891c>] kthread+0xb0/0xbc
> [<c0014510>] ret_from_fork+0x14/0x24
>
> other info that might help us debug this:
>
> Possible unsafe locking scenario:
>
> CPU0 CPU1
> ---- ----
> lock(console_lock);
> lock((fb_notifier_list).rwsem);
> lock(console_lock);
> lock((fb_notifier_list).rwsem);
>
> *** DEADLOCK ***
>
> 3 locks held by kworker/0:1/442:
> #0: (events){.+.+..}, at: [<c0040854>] process_one_work+0x140/0x4ec
> #1: (console_work){+.+...}, at: [<c0040854>] process_one_work+0x140/0x4ec
> #2: (console_lock){+.+.+.}, at: [<c01c2b48>] console_callback+0x14/0x138
>
> stack backtrace:
> Backtrace:
> [<c00185d8>] (dump_backtrace+0x0/0x10c) from [<c03294c8>] (dump_stack+0x18/0x1c)
> r6:c05323f0 r5:c0524800 r4:c05323f0 r3:cf8f6b80
> [<c03294b0>] (dump_stack+0x0/0x1c) from [<c006bda4>] (print_circular_bug+0x1e4/0x2f0)
> [<c006bbc0>] (print_circular_bug+0x0/0x2f0) from [<c006f458>] (__lock_acquire+0x1e0c/0x1e80)
> [<c006d64c>] (__lock_acquire+0x0/0x1e80) from [<c006fa04>] (lock_acquire+0x68/0x7c)
> [<c006f99c>] (lock_acquire+0x0/0x7c) from [<c032b3a0>] (down_read+0x34/0x44)
> r7:00000010 r6:cfb7dd20 r5:00000002 r4:c04715cc
> [<c032b36c>] (down_read+0x0/0x44) from [<c004ea48>] (__blocking_notifier_call_chain+0x34/0x68)
> r5:ffffffff r4:c04715cc
> [<c004ea14>] (__blocking_notifier_call_chain+0x0/0x68) from [<c004ea9c>] (blocking_notifier_call_chain+0x20/0x28)
> r7:cf0ffc00 r6:00000001 r5:cfb7dd20 r4:cfb5f800
> [<c004ea7c>] (blocking_notifier_call_chain+0x0/0x28) from [<c0196e70>] (fb_notifier_call_chain+0x20/0x28)
> [<c0196e50>] (fb_notifier_call_chain+0x0/0x28) from [<c0197690>] (fb_blank+0x40/0xac)
> [<c0197650>] (fb_blank+0x0/0xac) from [<c019f874>] (fbcon_blank+0x1f4/0x29c)
> r6:00000001 r5:cf80a000 r4:cfb5f800
> [<c019f680>] (fbcon_blank+0x0/0x29c) from [<c01c09e0>] (do_blank_screen+0x1b8/0x270)
> [<c01c0828>] (do_blank_screen+0x0/0x270) from [<c01c2ba8>] (console_callback+0x74/0x138)
> r7:c0ba8640 r6:c0bac300 r5:c099a51c r4:c099a51c
> [<c01c2b34>] (console_callback+0x0/0x138) from [<c00408c8>] (process_one_work+0x1b4/0x4ec)
> r6:c0bac300 r5:cf9489c0 r4:c0472e3c r3:c01c2b34
> [<c0040714>] (process_one_work+0x0/0x4ec) from [<c0043610>] (worker_thread+0x17c/0x4bc)
> [<c0043494>] (worker_thread+0x0/0x4bc) from [<c004891c>] (kthread+0xb0/0xbc)
> [<c004886c>] (kthread+0x0/0xbc) from [<c0014510>] (ret_from_fork+0x14/0x24)
> r8:00000000 r7:00000000 r6:00000000 r5:c004886c r4:cf84dde8
>
> --
> Russell King
> Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
> maintainer of:
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of:
^ permalink raw reply
* Re: [PATCH v5 3/8] video: mmp display controller support
From: Andrew Morton @ 2013-01-30 21:49 UTC (permalink / raw)
To: linux-fbdev
In-Reply-To: <1358411023-2667-1-git-send-email-zzhu3@marvell.com>
On Thu, 17 Jan 2013 16:23:43 +0800
Zhou Zhu <zzhu3@marvell.com> wrote:
> From: Guoqing Li <ligq@marvell.com>
>
> Marvell mmp series display controller support in mmpdisp subsystem.
> This driver focus on implementation of hardware operations of path/ovly,
> which is defined in mmp display subsystem interface.
> This driver registers all pathes to mmp display framework.
>
> ...
>
> +#define ovly_is_vid(ovly) (ovly->dmafetch_id % 2)
> +#define path_to_path_plat(path) \
> + ((struct mmphw_path_plat *)path->plat_data)
> +#define ovly_to_ctrl(ovly) \
> + (path_to_ctrl(ovly->path))
> +#define path_to_ctrl(path) \
> + (path_to_path_plat(path)->ctrl)
> +#define ctrl_regs(path) \
> + (path_to_ctrl(path)->reg_base)
These could be implemented in C rather than in CPP. The result would
be improved typechecking and improved readability.
>
> ...
>
^ permalink raw reply
* Re: [PATCH v5 2/8] video: mmp fb support
From: Andrew Morton @ 2013-01-30 21:49 UTC (permalink / raw)
To: linux-fbdev
In-Reply-To: <1358410998-2631-1-git-send-email-zzhu3@marvell.com>
On Thu, 17 Jan 2013 16:23:18 +0800
Zhou Zhu <zzhu3@marvell.com> wrote:
> Added fb support for Marvell mmp display subsystem.
> This driver is configured using "buffer driver mach info".
> With configured name of path, this driver get path using
> using exported interface of mmp display driver.
> Then this driver get ovly using configured id and operates
> on this ovly to show buffers on display devices.
>
> ...
>
> +static void *alloc_framebuffer(size_t size, dma_addr_t *dma)
> +{
> + int nr, i = 0;
> + struct page **pages;
> + void *start;
> +
> + nr = size >> PAGE_SHIFT;
If `size' is not a multiple of PAGE_SIZE, this will cause various
overruns later on.
> + start = alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO);
> + if (start = NULL)
> + return NULL;
> +
> + *dma = virt_to_phys(start);
> + pages = vmalloc(sizeof(struct page *) * nr);
> + if (pages = NULL)
> + return NULL;
Did we leak the memory at `start'?
> +
> + while (i < nr) {
> + pages[i] = phys_to_page(*dma + (i << PAGE_SHIFT));
> + i++;
> + }
> + start = vmap(pages, nr, 0, pgprot_writecombine(pgprot_kernel));
> +
> + vfree(pages);
> + return start;
> +}
>
> ...
>
> +static int mmpfb_set_par(struct fb_info *info)
> +{
> + struct mmpfb_info *fbi = info->par;
> + struct fb_var_screeninfo *var = &info->var;
> + struct mmp_addr addr;
> + struct mmp_win win;
> + struct mmp_mode mode;
> +
> + int ret = var_update(info);
Strange code layout here. I suggest:
struct mmp_win win;
struct mmp_mode mode;
int ret;
ret = var_update(info);
> + if (ret != 0)
> + return ret;
> +
> + /* set window/path according to new videomode */
> + fbmode_to_mmpmode(&mode, &fbi->mode, fbi->output_fmt);
> + mmp_path_set_mode(fbi->path, &mode);
> +
> + memset(&win, 0, sizeof(win));
> + win.xsrc = win.xdst = fbi->mode.xres;
> + win.ysrc = win.ydst = fbi->mode.yres;
> + win.pix_fmt = fbi->pix_fmt;
> + mmp_ovly_set_win(fbi->ovly, &win);
> +
> + /* set address always */
> + memset(&addr, 0, sizeof(addr));
> + addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset)
> + * var->bits_per_pixel / 8 + fbi->fb_start_dma;
> + mmp_ovly_set_addr(fbi->ovly, &addr);
> +
> + return 0;
> +}
>
> ...
>
^ permalink raw reply
* Re: [PATCH v5 1/8] video: mmp display subsystem
From: Andrew Morton @ 2013-01-30 21:49 UTC (permalink / raw)
To: linux-fbdev
In-Reply-To: <1358410985-2594-1-git-send-email-zzhu3@marvell.com>
On Thu, 17 Jan 2013 16:23:05 +0800
Zhou Zhu <zzhu3@marvell.com> wrote:
> Added mmp display subsystem to support Marvell MMP display controllers.
>
> This subsystem contains 4 parts:
> --fb folder
> --core.c
> --hw folder
> --panel folder
>
> 1. fb folder contains implementation of fb.
> fb get path and ovly from common interface and operates on these structures.
>
> 2. core.c provides common interface for a hardware abstraction.
> Major parts of this interface are:
> a) Path: path is a output device connected to a panel or HDMI TV.
> Main operations of the path is set/get timing/output color.
> fb operates output device through path structure.
> b) Ovly: Ovly is a buffer shown on the path.
> Ovly describes frame buffer and its source/destination size, offset, input
> color, buffer address, z-order, and so on.
> Each fb device maps to one ovly.
>
> 3. hw folder contains implementation of hardware operations defined by core.c.
> It registers paths for fb use.
>
> 4. panel folder contains implementation of panels.
> It's connected to path. Panel drivers would also regiester panels and linked
> to path when probe.
>
>
> ...
>
> +#define list_find(_item, _list, _field, _name)\
> + do {\
> + int found = 0;\
> + list_for_each_entry(_item, &_list, node) {\
> + dev_dbg(_item->dev, "checking %s, target %s",\
> + _item->_field, _name);\
> + if (strcmp(_name, _item->_field) = 0) {\
> + found = 1;\
> + break;\
> + } \
> + } \
> + if (!found)\
> + _item = NULL;\
> + } while (0)
ick.
This could have been implemented as a nice C function, I suspect.
> +/*
> + * panel list is used to pair panel/path when path/panel registered
> + * path list is used for both buffer driver and platdriver
> + * plat driver do path register/unregister
> + * panel driver do panel register/unregister
> + * buffer driver get registered path
> + */
> +static LIST_HEAD(panel_list);
> +static LIST_HEAD(path_list);
> +static DEFINE_MUTEX(disp_lock);
> +
> +int mmp_register_panel(struct mmp_panel *panel)
> +{
> + struct mmp_path *path;
> +
> + mutex_lock(&disp_lock);
> +
> + /* add */
> + list_add_tail(&panel->node, &panel_list);
> +
> + /* try to register to path */
> + list_find(path, path_list, name, panel->plat_path_name);
> + if (path) {
> + dev_info(panel->dev, "register to path %s\n",
> + panel->plat_path_name);
> + path->panel = panel;
> + }
> +
> + mutex_unlock(&disp_lock);
> + return 1;
> +}
> +EXPORT_SYMBOL_GPL(mmp_register_panel);
Should this always return success?
Should it return an error if the registration failed?
This function should return 0 on success or a negative errno on error.
This is a convention we very commonly use.
If this function really can only return success then let's make it a
void returning function.
> +void mmp_unregister_panel(struct mmp_panel *panel)
> +{
> + mutex_lock(&disp_lock);
> + list_del(&panel->node);
> + mutex_unlock(&disp_lock);
> +}
> +EXPORT_SYMBOL_GPL(mmp_unregister_panel);
> +
> +struct mmp_path *mmp_get_path(const char *name)
> +{
> + struct mmp_path *path;
> +
> + mutex_lock(&disp_lock);
> + list_find(path, path_list, name, name);
> + mutex_unlock(&disp_lock);
> +
> + return path;
> +}
> +EXPORT_SYMBOL_GPL(mmp_get_path);
> +
> +struct mmp_path *mmp_register_path(struct mmp_path_info *info)
It's generally desirable to document at least the exported interfaces.
> +{
> + int i, size;
A more appropriate type for `size' is size_t.
> + struct mmp_path *path = NULL;
> + struct mmp_panel *panel;
> +
> + size = sizeof(struct mmp_path)
> + + sizeof(struct mmp_ovly) * info->ovly_num;
> + path = kzalloc(size, GFP_KERNEL);
> + if (!path)
> + goto failed;
> +
> + /* path set */
> + path->ovlys = (void *)path + sizeof(struct mmp_path);
A trick we often use is to put a zero-length array at the end of the
struct. So `struct mmp_path' gets:
struct mmp_ovly overlays[0];
}
added to it. Then you'll find that the mmp_path.ovlys field can just
go away and the code will become shorter and more efficient.
I wish you haven't used "ovly". The kernel has a strong tradition of
spelling things out in full and avoiding the weird abbreviations. Just
use "overlay". The code becomes mroe readable and there's never a
problem remembering the name of the identifiers.
> + mutex_init(&path->access_ok);
> + path->dev = info->dev;
> + path->id = info->id;
> + path->name = info->name;
> + path->output_type = info->output_type;
> + path->ovly_num = info->ovly_num;
> + path->plat_data = info->plat_data;
> + path->ops.set_mode = info->set_mode;
>
> ...
>
^ permalink raw reply
* Re: [PATCH v5 0/8] video: mmp display subsystem
From: Andrew Morton @ 2013-01-30 21:49 UTC (permalink / raw)
To: linux-fbdev
In-Reply-To: <1358410826-2557-1-git-send-email-zzhu3@marvell.com>
On Thu, 17 Jan 2013 16:20:26 +0800
Zhou Zhu <zzhu3@marvell.com> wrote:
> This series added support for display controller in
> Marvell "mmp" series processors.
> It also contains patches to enable display panel on
> TTC_DKB board.
I'll put these into -mm so they get a bit of linux-next exposure.
I'd really prefer not to merge them myself - it's been a few years
since I did fbdev things and I seem to have forgotten most of it :(.
Hopefully Florian or some other fbdev person can comment on this
patchset.
I had a few minorish comments on code details.
^ permalink raw reply
* Re: [RFC 1/4] video: panel: add CLAA101WA01A panel support
From: Stephen Warren @ 2013-01-30 20:30 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Laurent Pinchart, Thierry Reding, Mark Zhang,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
linux-tegra-u79uwXL29TY76Z2rM5mHXA, gnurou-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1359514939-15653-2-git-send-email-acourbot-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
On 01/29/2013 08:02 PM, Alexandre Courbot wrote:
> Add support for the Chunghwa CLAA101WA01A display panel.
Since this defines a new DT binding, especially since it's for a new
class of device, this patch should be Cc:
devicetree-discuss@lists.ozlabs.org too, next time you post.
^ permalink raw reply
* Re: [RFC 1/4] video: panel: add CLAA101WA01A panel support
From: Stephen Warren @ 2013-01-30 20:27 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Laurent Pinchart, Thierry Reding, Mark Zhang,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
linux-tegra-u79uwXL29TY76Z2rM5mHXA, gnurou-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1359514939-15653-2-git-send-email-acourbot-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
On 01/29/2013 08:02 PM, Alexandre Courbot wrote:
> Add support for the Chunghwa CLAA101WA01A display panel.
> diff --git a/Documentation/devicetree/bindings/video/display/chunghwa,claa101wa01a.txt b/Documentation/devicetree/bindings/video/display/chunghwa,claa101wa01a.txt
> +Chunghwa CLAA101WA01A Display Panel
> +
> +Required properties:
> +- compatible: "chunghwa,claa101wa01a"
> +- pnl-supply: regulator controlling power supply to the panel
> +- bl-supply: regulator controlling power supply to the backlight
> +- pnl-enable-gpios: GPIO that enables the panel
> +- bl-enable-gpios: GPIO that enables the backlight
So this looks like a reasonable binding to me. The one issue is that
it's very generic, and if we go this route, we'll probably end up with
tens or hundreds of identical or extremely similar simple bindings, and
associated drivers.
We can avoid this instead by defining something like a "simple-lcd"
binding, and associated driver, that will work for perhaps 90%, 95%,
99%, even 100%(?) of panels. Just like the above, but with:
compatible="simple-panel", "chunghwa,claa101wa01a"
instead, and the driver binding to "simple-panel" rather than
"chunghwa,claa101wa01a".
The driver can assume that a specific set of supplies (and perhaps
GPIOs) is always present, and that the /sequence/ of manipulating those
is fixed. This will avoid the need for anything like the power sequences
code. If a particular panel doesn't fit those assumptions, including the
exact sequence of manipulations for each state transition (which should
be documented in the binding) then it can get a custom driver, this also
avoiding having to define custom sequences in DT.
Things that might be parameterized/optional:
* Perhaps some GPIOs aren't always present.
* If some regulators aren't SW-controllable, DT should still provide a
fixed/dummy regulator so the driver doesn't care.
* Wait times between regulator/GPIO/... manipulation could be specified
in DT.
* For panels without EDID, CDF DT bindings can provide the list of
supported modes, otherwise we assume that the display controller that
drives the panel has been told how to access the EDID, just like it
would for an "external" display.
^ permalink raw reply
* Re: [RFC 1/4] video: panel: add CLAA101WA01A panel support
From: Stephen Warren @ 2013-01-30 20:19 UTC (permalink / raw)
To: Mark Zhang
Cc: Alexandre Courbot, Laurent Pinchart, Thierry Reding, Mark Zhang,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
linux-tegra-u79uwXL29TY76Z2rM5mHXA, gnurou-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <5108C9C1.1090707-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On 01/30/2013 12:20 AM, Mark Zhang wrote:
> On 01/30/2013 11:02 AM, Alexandre Courbot wrote:
>> Add support for the Chunghwa CLAA101WA01A display panel.
>> +static int panel_claa101_get_modes(struct display_entity *entity,
>> + const struct videomode **modes)
>> +{
>> + /* TODO get modes from EDID? */
>
> Why not move the "nvidia,ddc" from encoder's DT to panel's DT? In that
> case, you can get EDID here. I know drm has some helpers to fetch EDID
> but I recall there are some other functions which has no drm
> dependencies which may be suitable for you.
DDC access is a property of the display controller, not the panel
itself. The panel might be hooked up to a display controller's DDC/I2C
channel as the target, but it isn't the host/controller of the DDC/I2C
channel. As such, placing the nvidia,ddc property into the display
controller node makes sense.
Re: the other discussion in this thread: Probably the simplest is if
tegradrm/other-CDF-users do something like:
1) If DDC I2C channel available for this display channel, query DDC.
2) If not, or perhaps also if that fails, query the panel driver for the
mode list.
That would cover all bases very simply.
^ permalink raw reply
* BUG: circular locking dependency detected
From: Russell King @ 2013-01-30 20:06 UTC (permalink / raw)
To: Florian Tobias Schandinat, linux-fbdev, linux-kernel, akpm
In-Reply-To: <20130130200400.GB31748@flint.arm.linux.org.uk>
This looks like a bug in the framebuffer/console layers. Looks like
we have one path where we call the notifier list, and a called
function takes the console lock, and another path where we hold the
console lock while calling the notifier list.
===========================
[ INFO: possible circular locking dependency detected ]
3.8.0-rc4+ #656 Not tainted
-------------------------------------------------------
kworker/0:1/442 is trying to acquire lock:
((fb_notifier_list).rwsem){.+.+.+}, at: [<c004ea48>] __blocking_notifier_call_chain+0x34/0x68
but task is already holding lock:
(console_lock){+.+.+.}, at: [<c01c2b48>] console_callback+0x14/0x138
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (console_lock){+.+.+.}:
[<c006f36c>] __lock_acquire+0x1d20/0x1e80
[<c006fa04>] lock_acquire+0x68/0x7c
[<c002a894>] console_lock+0x5c/0x70
[<c01c0adc>] register_con_driver+0x44/0x150
[<c01c1158>] take_over_console+0x24/0x3b4
[<c019d778>] fbcon_takeover+0x70/0xd4
[<c01a3108>] fbcon_event_notify+0x7c8/0x818
[<c004e538>] notifier_call_chain+0x4c/0x8c
[<c004ea64>] __blocking_notifier_call_chain+0x50/0x68
[<c004ea9c>] blocking_notifier_call_chain+0x20/0x28
[<c0196e70>] fb_notifier_call_chain+0x20/0x28
[<c0198194>] register_framebuffer+0x18c/0x238
[<c01a6148>] clcdfb_probe+0x2b0/0x3c0
[<c01a6da4>] amba_probe+0x88/0xa0
[<c01d1730>] driver_probe_device+0x84/0x218
[<c01d1960>] __driver_attach+0x9c/0xa0
[<c01cfe5c>] bus_for_each_dev+0x5c/0x88
[<c01d1398>] driver_attach+0x20/0x28
[<c01d0ddc>] bus_add_driver+0xa4/0x244
[<c01d1ed4>] driver_register+0x80/0x14c
[<c01a6848>] amba_driver_register+0x48/0x5c
[<c043aae0>] amba_clcdfb_init+0x28/0x3c
[<c000868c>] do_one_initcall+0x44/0x1ac
[<c0425928>] kernel_init_freeable+0x104/0x1c8
[<c03242ec>] kernel_init+0x10/0xec
[<c0014510>] ret_from_fork+0x14/0x24
-> #0 ((fb_notifier_list).rwsem){.+.+.+}:
[<c006bc44>] print_circular_bug+0x84/0x2f0
[<c006f458>] __lock_acquire+0x1e0c/0x1e80
[<c006fa04>] lock_acquire+0x68/0x7c
[<c032b3a0>] down_read+0x34/0x44
[<c004ea48>] __blocking_notifier_call_chain+0x34/0x68
[<c004ea9c>] blocking_notifier_call_chain+0x20/0x28
[<c0196e70>] fb_notifier_call_chain+0x20/0x28
[<c0197690>] fb_blank+0x40/0xac
[<c019f874>] fbcon_blank+0x1f4/0x29c
[<c01c09e0>] do_blank_screen+0x1b8/0x270
[<c01c2ba8>] console_callback+0x74/0x138
[<c00408c8>] process_one_work+0x1b4/0x4ec
[<c0043610>] worker_thread+0x17c/0x4bc
[<c004891c>] kthread+0xb0/0xbc
[<c0014510>] ret_from_fork+0x14/0x24
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(console_lock);
lock((fb_notifier_list).rwsem);
lock(console_lock);
lock((fb_notifier_list).rwsem);
*** DEADLOCK ***
3 locks held by kworker/0:1/442:
#0: (events){.+.+..}, at: [<c0040854>] process_one_work+0x140/0x4ec
#1: (console_work){+.+...}, at: [<c0040854>] process_one_work+0x140/0x4ec
#2: (console_lock){+.+.+.}, at: [<c01c2b48>] console_callback+0x14/0x138
stack backtrace:
Backtrace:
[<c00185d8>] (dump_backtrace+0x0/0x10c) from [<c03294c8>] (dump_stack+0x18/0x1c)
r6:c05323f0 r5:c0524800 r4:c05323f0 r3:cf8f6b80
[<c03294b0>] (dump_stack+0x0/0x1c) from [<c006bda4>] (print_circular_bug+0x1e4/0x2f0)
[<c006bbc0>] (print_circular_bug+0x0/0x2f0) from [<c006f458>] (__lock_acquire+0x1e0c/0x1e80)
[<c006d64c>] (__lock_acquire+0x0/0x1e80) from [<c006fa04>] (lock_acquire+0x68/0x7c)
[<c006f99c>] (lock_acquire+0x0/0x7c) from [<c032b3a0>] (down_read+0x34/0x44)
r7:00000010 r6:cfb7dd20 r5:00000002 r4:c04715cc
[<c032b36c>] (down_read+0x0/0x44) from [<c004ea48>] (__blocking_notifier_call_chain+0x34/0x68)
r5:ffffffff r4:c04715cc
[<c004ea14>] (__blocking_notifier_call_chain+0x0/0x68) from [<c004ea9c>] (blocking_notifier_call_chain+0x20/0x28)
r7:cf0ffc00 r6:00000001 r5:cfb7dd20 r4:cfb5f800
[<c004ea7c>] (blocking_notifier_call_chain+0x0/0x28) from [<c0196e70>] (fb_notifier_call_chain+0x20/0x28)
[<c0196e50>] (fb_notifier_call_chain+0x0/0x28) from [<c0197690>] (fb_blank+0x40/0xac)
[<c0197650>] (fb_blank+0x0/0xac) from [<c019f874>] (fbcon_blank+0x1f4/0x29c)
r6:00000001 r5:cf80a000 r4:cfb5f800
[<c019f680>] (fbcon_blank+0x0/0x29c) from [<c01c09e0>] (do_blank_screen+0x1b8/0x270)
[<c01c0828>] (do_blank_screen+0x0/0x270) from [<c01c2ba8>] (console_callback+0x74/0x138)
r7:c0ba8640 r6:c0bac300 r5:c099a51c r4:c099a51c
[<c01c2b34>] (console_callback+0x0/0x138) from [<c00408c8>] (process_one_work+0x1b4/0x4ec)
r6:c0bac300 r5:cf9489c0 r4:c0472e3c r3:c01c2b34
[<c0040714>] (process_one_work+0x0/0x4ec) from [<c0043610>] (worker_thread+0x17c/0x4bc)
[<c0043494>] (worker_thread+0x0/0x4bc) from [<c004891c>] (kthread+0xb0/0xbc)
[<c004886c>] (kthread+0x0/0xbc) from [<c0014510>] (ret_from_fork+0x14/0x24)
r8:00000000 r7:00000000 r6:00000000 r5:c004886c r4:cf84dde8
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of:
^ permalink raw reply
* BUG: circular locking dependency detected
From: Russell King @ 2013-01-30 20:04 UTC (permalink / raw)
To: linux-fbdev
This looks like a bug in the framebuffer/console layers. Looks like
we have one path where we call the notifier list, and a called
function takes the console lock, and another path where we hold the
console lock while calling the notifier list.
===========================
[ INFO: possible circular locking dependency detected ]
3.8.0-rc4+ #656 Not tainted
-------------------------------------------------------
kworker/0:1/442 is trying to acquire lock:
((fb_notifier_list).rwsem){.+.+.+}, at: [<c004ea48>] __blocking_notifier_call_chain+0x34/0x68
but task is already holding lock:
(console_lock){+.+.+.}, at: [<c01c2b48>] console_callback+0x14/0x138
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (console_lock){+.+.+.}:
[<c006f36c>] __lock_acquire+0x1d20/0x1e80
[<c006fa04>] lock_acquire+0x68/0x7c
[<c002a894>] console_lock+0x5c/0x70
[<c01c0adc>] register_con_driver+0x44/0x150
[<c01c1158>] take_over_console+0x24/0x3b4
[<c019d778>] fbcon_takeover+0x70/0xd4
[<c01a3108>] fbcon_event_notify+0x7c8/0x818
[<c004e538>] notifier_call_chain+0x4c/0x8c
[<c004ea64>] __blocking_notifier_call_chain+0x50/0x68
[<c004ea9c>] blocking_notifier_call_chain+0x20/0x28
[<c0196e70>] fb_notifier_call_chain+0x20/0x28
[<c0198194>] register_framebuffer+0x18c/0x238
[<c01a6148>] clcdfb_probe+0x2b0/0x3c0
[<c01a6da4>] amba_probe+0x88/0xa0
[<c01d1730>] driver_probe_device+0x84/0x218
[<c01d1960>] __driver_attach+0x9c/0xa0
[<c01cfe5c>] bus_for_each_dev+0x5c/0x88
[<c01d1398>] driver_attach+0x20/0x28
[<c01d0ddc>] bus_add_driver+0xa4/0x244
[<c01d1ed4>] driver_register+0x80/0x14c
[<c01a6848>] amba_driver_register+0x48/0x5c
[<c043aae0>] amba_clcdfb_init+0x28/0x3c
[<c000868c>] do_one_initcall+0x44/0x1ac
[<c0425928>] kernel_init_freeable+0x104/0x1c8
[<c03242ec>] kernel_init+0x10/0xec
[<c0014510>] ret_from_fork+0x14/0x24
-> #0 ((fb_notifier_list).rwsem){.+.+.+}:
[<c006bc44>] print_circular_bug+0x84/0x2f0
[<c006f458>] __lock_acquire+0x1e0c/0x1e80
[<c006fa04>] lock_acquire+0x68/0x7c
[<c032b3a0>] down_read+0x34/0x44
[<c004ea48>] __blocking_notifier_call_chain+0x34/0x68
[<c004ea9c>] blocking_notifier_call_chain+0x20/0x28
[<c0196e70>] fb_notifier_call_chain+0x20/0x28
[<c0197690>] fb_blank+0x40/0xac
[<c019f874>] fbcon_blank+0x1f4/0x29c
[<c01c09e0>] do_blank_screen+0x1b8/0x270
[<c01c2ba8>] console_callback+0x74/0x138
[<c00408c8>] process_one_work+0x1b4/0x4ec
[<c0043610>] worker_thread+0x17c/0x4bc
[<c004891c>] kthread+0xb0/0xbc
[<c0014510>] ret_from_fork+0x14/0x24
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(console_lock);
lock((fb_notifier_list).rwsem);
lock(console_lock);
lock((fb_notifier_list).rwsem);
*** DEADLOCK ***
3 locks held by kworker/0:1/442:
#0: (events){.+.+..}, at: [<c0040854>] process_one_work+0x140/0x4ec
#1: (console_work){+.+...}, at: [<c0040854>] process_one_work+0x140/0x4ec
#2: (console_lock){+.+.+.}, at: [<c01c2b48>] console_callback+0x14/0x138
stack backtrace:
Backtrace:
[<c00185d8>] (dump_backtrace+0x0/0x10c) from [<c03294c8>] (dump_stack+0x18/0x1c)
r6:c05323f0 r5:c0524800 r4:c05323f0 r3:cf8f6b80
[<c03294b0>] (dump_stack+0x0/0x1c) from [<c006bda4>] (print_circular_bug+0x1e4/0x2f0)
[<c006bbc0>] (print_circular_bug+0x0/0x2f0) from [<c006f458>] (__lock_acquire+0x1e0c/0x1e80)
[<c006d64c>] (__lock_acquire+0x0/0x1e80) from [<c006fa04>] (lock_acquire+0x68/0x7c)
[<c006f99c>] (lock_acquire+0x0/0x7c) from [<c032b3a0>] (down_read+0x34/0x44)
r7:00000010 r6:cfb7dd20 r5:00000002 r4:c04715cc
[<c032b36c>] (down_read+0x0/0x44) from [<c004ea48>] (__blocking_notifier_call_chain+0x34/0x68)
r5:ffffffff r4:c04715cc
[<c004ea14>] (__blocking_notifier_call_chain+0x0/0x68) from [<c004ea9c>] (blocking_notifier_call_chain+0x20/0x28)
r7:cf0ffc00 r6:00000001 r5:cfb7dd20 r4:cfb5f800
[<c004ea7c>] (blocking_notifier_call_chain+0x0/0x28) from [<c0196e70>] (fb_notifier_call_chain+0x20/0x28)
[<c0196e50>] (fb_notifier_call_chain+0x0/0x28) from [<c0197690>] (fb_blank+0x40/0xac)
[<c0197650>] (fb_blank+0x0/0xac) from [<c019f874>] (fbcon_blank+0x1f4/0x29c)
r6:00000001 r5:cf80a000 r4:cfb5f800
[<c019f680>] (fbcon_blank+0x0/0x29c) from [<c01c09e0>] (do_blank_screen+0x1b8/0x270)
[<c01c0828>] (do_blank_screen+0x0/0x270) from [<c01c2ba8>] (console_callback+0x74/0x138)
r7:c0ba8640 r6:c0bac300 r5:c099a51c r4:c099a51c
[<c01c2b34>] (console_callback+0x0/0x138) from [<c00408c8>] (process_one_work+0x1b4/0x4ec)
r6:c0bac300 r5:cf9489c0 r4:c0472e3c r3:c01c2b34
[<c0040714>] (process_one_work+0x0/0x4ec) from [<c0043610>] (worker_thread+0x17c/0x4bc)
[<c0043494>] (worker_thread+0x0/0x4bc) from [<c004891c>] (kthread+0xb0/0xbc)
[<c004886c>] (kthread+0x0/0xbc) from [<c0014510>] (ret_from_fork+0x14/0x24)
r8:00000000 r7:00000000 r6:00000000 r5:c004886c r4:cf84dde8
--
Russell King
^ permalink raw reply
* [RFC PATCH 4/4] video: display: Add Samsung s6e8ax0 display panel driver
From: Tomasz Figa @ 2013-01-30 15:39 UTC (permalink / raw)
To: dri-devel
Cc: linux-fbdev, linux-samsung-soc, kyungmin.park, m.szyprowski,
t.figa, tomasz.figa, Daniel Vetter, Marcus Lorentzon,
Laurent Pinchart, rob, tomi.valkeinen, Vikas Sajjan, inki.dae,
dh09.lee, ville.syrjala, s.nawrocki
In-Reply-To: <1359560343-31636-1-git-send-email-t.figa@samsung.com>
This patch adds Common Display Framework driver for Samsung s6e8ax0
MIPI DSI display panel.
Signed-off-by: Tomasz Figa <t.figa@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/video/display/Kconfig | 3 +
drivers/video/display/Makefile | 1 +
drivers/video/display/panel-s6e8ax0.c | 1027 +++++++++++++++++++++++++++++++++
include/video/panel-s6e8ax0.h | 41 ++
4 files changed, 1072 insertions(+)
create mode 100644 drivers/video/display/panel-s6e8ax0.c
create mode 100644 include/video/panel-s6e8ax0.h
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig
index b14527a..f19ec04 100644
--- a/drivers/video/display/Kconfig
+++ b/drivers/video/display/Kconfig
@@ -5,6 +5,9 @@ menuconfig DISPLAY_CORE
if DISPLAY_CORE
+config DISPLAY_PANEL_S6E8AX0
+ tristate "S6E8AX0 DSI video mode panel"
+ select OF_VIDEOMODE
config DISPLAY_SOURCE_EXYNOS_DSI
tristate "Samsung SoC MIPI DSI Master"
diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile
index 40a283a..0f7fdc2 100644
--- a/drivers/video/display/Makefile
+++ b/drivers/video/display/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_DISPLAY_CORE) += display-core.o
+obj-$(CONFIG_DISPLAY_PANEL_S6E8AX0) += panel-s6e8ax0.o
obj-$(CONFIG_DISPLAY_SOURCE_EXYNOS_DSI) += source-exynos_dsi.o
diff --git a/drivers/video/display/panel-s6e8ax0.c b/drivers/video/display/panel-s6e8ax0.c
new file mode 100644
index 0000000..4c09fe2
--- /dev/null
+++ b/drivers/video/display/panel-s6e8ax0.c
@@ -0,0 +1,1027 @@
+/* linux/drivers/video/exynos/s6e8ax0.c
+ *
+ * MIPI-DSI based s6e8ax0 AMOLED lcd 4.65 inch panel driver.
+ *
+ * Inki Dae, <inki.dae@samsung.com>
+ * Donghwa Lee, <dh09.lee@samsung.com>
+ * Tomasz Figa, <t.figa@samsung.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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/ctype.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+
+#include <linux/fb.h>
+#include <linux/gpio.h>
+#include <linux/lcd.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_videomode.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/backlight.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/display.h>
+#include <video/mipi_display.h>
+#include <video/panel-s6e8ax0.h>
+
+#define LDI_MTP_LENGTH 24
+#define DSIM_PM_STABLE_TIME 10
+#define MIN_BRIGHTNESS 0
+#define MAX_BRIGHTNESS 24
+#define GAMMA_TABLE_COUNT 26
+
+struct s6e8ax0 {
+ struct display_entity entity;
+ struct device *dev;
+
+ struct s6e8ax0_platform_data *pdata;
+ struct backlight_device *bd;
+ struct lcd_device *ld;
+ struct regulator_bulk_data supplies[2];
+
+ bool enabled;
+ unsigned int id;
+ unsigned int gamma;
+ unsigned int acl_enable;
+ unsigned int cur_acl;
+ int power;
+
+ unsigned int reset_gpio;
+};
+
+#define to_panel(p) container_of(p, struct s6e8ax0, entity)
+
+static const unsigned char s6e8ax0_22_gamma_30[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xf5, 0x00, 0xff, 0xad, 0xaf,
+ 0xbA, 0xc3, 0xd8, 0xc5, 0x9f, 0xc6, 0x9e, 0xc1, 0xdc, 0xc0,
+ 0x00, 0x61, 0x00, 0x5a, 0x00, 0x74,
+};
+
+static const unsigned char s6e8ax0_22_gamma_50[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xe8, 0x1f, 0xf7, 0xad, 0xc0,
+ 0xb5, 0xc4, 0xdc, 0xc4, 0x9e, 0xc6, 0x9c, 0xbb, 0xd8, 0xbb,
+ 0x00, 0x70, 0x00, 0x68, 0x00, 0x86,
+};
+
+static const unsigned char s6e8ax0_22_gamma_60[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xde, 0x1f, 0xef, 0xad, 0xc4,
+ 0xb3, 0xc3, 0xdd, 0xc4, 0x9e, 0xc6, 0x9c, 0xbc, 0xd6, 0xba,
+ 0x00, 0x75, 0x00, 0x6e, 0x00, 0x8d,
+};
+
+static const unsigned char s6e8ax0_22_gamma_70[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xd8, 0x1f, 0xe7, 0xaf, 0xc8,
+ 0xb4, 0xc4, 0xdd, 0xc3, 0x9d, 0xc6, 0x9c, 0xbb, 0xd6, 0xb9,
+ 0x00, 0x7a, 0x00, 0x72, 0x00, 0x93,
+};
+
+static const unsigned char s6e8ax0_22_gamma_80[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xc9, 0x1f, 0xde, 0xae, 0xc9,
+ 0xb1, 0xc3, 0xdd, 0xc2, 0x9d, 0xc5, 0x9b, 0xbc, 0xd6, 0xbb,
+ 0x00, 0x7f, 0x00, 0x77, 0x00, 0x99,
+};
+
+static const unsigned char s6e8ax0_22_gamma_90[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xc7, 0x1f, 0xd9, 0xb0, 0xcc,
+ 0xb2, 0xc3, 0xdc, 0xc1, 0x9c, 0xc6, 0x9c, 0xbc, 0xd4, 0xb9,
+ 0x00, 0x83, 0x00, 0x7b, 0x00, 0x9e,
+};
+
+static const unsigned char s6e8ax0_22_gamma_100[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xbd, 0x80, 0xcd, 0xba, 0xce,
+ 0xb3, 0xc4, 0xde, 0xc3, 0x9c, 0xc4, 0x9, 0xb8, 0xd3, 0xb6,
+ 0x00, 0x88, 0x00, 0x80, 0x00, 0xa5,
+};
+
+static const unsigned char s6e8ax0_22_gamma_120[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb9, 0x95, 0xc8, 0xb1, 0xcf,
+ 0xb2, 0xc6, 0xdf, 0xc5, 0x9b, 0xc3, 0x99, 0xb6, 0xd2, 0xb6,
+ 0x00, 0x8f, 0x00, 0x86, 0x00, 0xac,
+};
+
+static const unsigned char s6e8ax0_22_gamma_130[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb7, 0xa0, 0xc7, 0xb1, 0xd0,
+ 0xb2, 0xc4, 0xdd, 0xc3, 0x9a, 0xc3, 0x98, 0xb6, 0xd0, 0xb4,
+ 0x00, 0x92, 0x00, 0x8a, 0x00, 0xb1,
+};
+
+static const unsigned char s6e8ax0_22_gamma_140[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb7, 0xa0, 0xc5, 0xb2, 0xd0,
+ 0xb3, 0xc3, 0xde, 0xc3, 0x9b, 0xc2, 0x98, 0xb6, 0xd0, 0xb4,
+ 0x00, 0x95, 0x00, 0x8d, 0x00, 0xb5,
+};
+
+static const unsigned char s6e8ax0_22_gamma_150[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xa0, 0xc2, 0xb2, 0xd0,
+ 0xb2, 0xc1, 0xdd, 0xc2, 0x9b, 0xc2, 0x98, 0xb4, 0xcf, 0xb1,
+ 0x00, 0x99, 0x00, 0x90, 0x00, 0xba,
+};
+
+static const unsigned char s6e8ax0_22_gamma_160[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xa5, 0xbf, 0xb0, 0xd0,
+ 0xb1, 0xc3, 0xde, 0xc2, 0x99, 0xc1, 0x97, 0xb4, 0xce, 0xb1,
+ 0x00, 0x9c, 0x00, 0x93, 0x00, 0xbe,
+};
+
+static const unsigned char s6e8ax0_22_gamma_170[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb5, 0xbf, 0xb1, 0xd1,
+ 0xb1, 0xc3, 0xde, 0xc3, 0x99, 0xc0, 0x96, 0xb4, 0xce, 0xb1,
+ 0x00, 0x9f, 0x00, 0x96, 0x00, 0xc2,
+};
+
+static const unsigned char s6e8ax0_22_gamma_180[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb7, 0xbe, 0xb3, 0xd2,
+ 0xb3, 0xc3, 0xde, 0xc2, 0x97, 0xbf, 0x95, 0xb4, 0xcd, 0xb1,
+ 0x00, 0xa2, 0x00, 0x99, 0x00, 0xc5,
+};
+
+static const unsigned char s6e8ax0_22_gamma_190[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb9, 0xbe, 0xb2, 0xd2,
+ 0xb2, 0xc3, 0xdd, 0xc3, 0x98, 0xbf, 0x95, 0xb2, 0xcc, 0xaf,
+ 0x00, 0xa5, 0x00, 0x9c, 0x00, 0xc9,
+};
+
+static const unsigned char s6e8ax0_22_gamma_200[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb9, 0xbc, 0xb2, 0xd2,
+ 0xb1, 0xc4, 0xdd, 0xc3, 0x97, 0xbe, 0x95, 0xb1, 0xcb, 0xae,
+ 0x00, 0xa8, 0x00, 0x9f, 0x00, 0xcd,
+};
+
+static const unsigned char s6e8ax0_22_gamma_210[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc1, 0xbd, 0xb1, 0xd1,
+ 0xb1, 0xc2, 0xde, 0xc2, 0x97, 0xbe, 0x94, 0xB0, 0xc9, 0xad,
+ 0x00, 0xae, 0x00, 0xa4, 0x00, 0xd4,
+};
+
+static const unsigned char s6e8ax0_22_gamma_220[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc7, 0xbd, 0xb1, 0xd1,
+ 0xb1, 0xc2, 0xdd, 0xc2, 0x97, 0xbd, 0x94, 0xb0, 0xc9, 0xad,
+ 0x00, 0xad, 0x00, 0xa2, 0x00, 0xd3,
+};
+
+static const unsigned char s6e8ax0_22_gamma_230[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc3, 0xbd, 0xb2, 0xd1,
+ 0xb1, 0xc3, 0xdd, 0xc1, 0x96, 0xbd, 0x94, 0xb0, 0xc9, 0xad,
+ 0x00, 0xb0, 0x00, 0xa7, 0x00, 0xd7,
+};
+
+static const unsigned char s6e8ax0_22_gamma_240[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xcb, 0xbd, 0xb1, 0xd2,
+ 0xb1, 0xc3, 0xdD, 0xc2, 0x95, 0xbd, 0x93, 0xaf, 0xc8, 0xab,
+ 0x00, 0xb3, 0x00, 0xa9, 0x00, 0xdb,
+};
+
+static const unsigned char s6e8ax0_22_gamma_250[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xcc, 0xbe, 0xb0, 0xd2,
+ 0xb0, 0xc3, 0xdD, 0xc2, 0x94, 0xbc, 0x92, 0xae, 0xc8, 0xab,
+ 0x00, 0xb6, 0x00, 0xab, 0x00, 0xde,
+};
+
+static const unsigned char s6e8ax0_22_gamma_260[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xd0, 0xbe, 0xaf, 0xd1,
+ 0xaf, 0xc2, 0xdd, 0xc1, 0x96, 0xbc, 0x93, 0xaf, 0xc8, 0xac,
+ 0x00, 0xb7, 0x00, 0xad, 0x00, 0xe0,
+};
+
+static const unsigned char s6e8ax0_22_gamma_270[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb2, 0xcF, 0xbd, 0xb0, 0xd2,
+ 0xaf, 0xc2, 0xdc, 0xc1, 0x95, 0xbd, 0x93, 0xae, 0xc6, 0xaa,
+ 0x00, 0xba, 0x00, 0xb0, 0x00, 0xe4,
+};
+
+static const unsigned char s6e8ax0_22_gamma_280[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb2, 0xd0, 0xbd, 0xaf, 0xd0,
+ 0xad, 0xc4, 0xdd, 0xc3, 0x95, 0xbd, 0x93, 0xac, 0xc5, 0xa9,
+ 0x00, 0xbd, 0x00, 0xb2, 0x00, 0xe7,
+};
+
+static const unsigned char s6e8ax0_22_gamma_300[] = {
+ 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb5, 0xd3, 0xbd, 0xb1, 0xd2,
+ 0xb0, 0xc0, 0xdc, 0xc0, 0x94, 0xba, 0x91, 0xac, 0xc5, 0xa9,
+ 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xed,
+};
+
+static const unsigned char *s6e8ax0_22_gamma_table[] = {
+ s6e8ax0_22_gamma_30,
+ s6e8ax0_22_gamma_50,
+ s6e8ax0_22_gamma_60,
+ s6e8ax0_22_gamma_70,
+ s6e8ax0_22_gamma_80,
+ s6e8ax0_22_gamma_90,
+ s6e8ax0_22_gamma_100,
+ s6e8ax0_22_gamma_120,
+ s6e8ax0_22_gamma_130,
+ s6e8ax0_22_gamma_140,
+ s6e8ax0_22_gamma_150,
+ s6e8ax0_22_gamma_160,
+ s6e8ax0_22_gamma_170,
+ s6e8ax0_22_gamma_180,
+ s6e8ax0_22_gamma_190,
+ s6e8ax0_22_gamma_200,
+ s6e8ax0_22_gamma_210,
+ s6e8ax0_22_gamma_220,
+ s6e8ax0_22_gamma_230,
+ s6e8ax0_22_gamma_240,
+ s6e8ax0_22_gamma_250,
+ s6e8ax0_22_gamma_260,
+ s6e8ax0_22_gamma_270,
+ s6e8ax0_22_gamma_280,
+ s6e8ax0_22_gamma_300,
+};
+
+static void s6e8ax0_panel_cond(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xf8, 0x3d, 0x35, 0x00, 0x00, 0x00, 0x93, 0x00, 0x3c, 0x7d,
+ 0x08, 0x27, 0x7d, 0x3f, 0x00, 0x00, 0x00, 0x20, 0x04, 0x08,
+ 0x6e, 0x00, 0x00, 0x00, 0x02, 0x08, 0x08, 0x23, 0x23, 0xc0,
+ 0xc8, 0x08, 0x48, 0xc1, 0x00, 0xc1, 0xff, 0xff, 0xc8
+ };
+ static const unsigned char data_to_send_panel_reverse[] = {
+ 0xf8, 0x19, 0x35, 0x00, 0x00, 0x00, 0x93, 0x00, 0x3c, 0x7d,
+ 0x08, 0x27, 0x7d, 0x3f, 0x00, 0x00, 0x00, 0x20, 0x04, 0x08,
+ 0x6e, 0x00, 0x00, 0x00, 0x02, 0x08, 0x08, 0x23, 0x23, 0xc0,
+ 0xc1, 0x01, 0x41, 0xc1, 0x00, 0xc1, 0xf6, 0xf6, 0xc1
+ };
+
+ if (lcd->pdata->panel_reverse)
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send_panel_reverse,
+ ARRAY_SIZE(data_to_send_panel_reverse));
+ else
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_display_cond(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xf2, 0x80, 0x03, 0x0d
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+/* Gamma 2.2 Setting (200cd, 7500K, 10MPCD) */
+static void s6e8ax0_gamma_cond(struct s6e8ax0 *lcd)
+{
+ unsigned int gamma = lcd->bd->props.brightness;
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ s6e8ax0_22_gamma_table[gamma],
+ GAMMA_TABLE_COUNT);
+}
+
+static void s6e8ax0_gamma_update(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xf7, 0x03
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0, data_to_send,
+ ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_etc_cond1(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xd1, 0xfe, 0x80, 0x00, 0x01, 0x0b, 0x00, 0x00, 0x40,
+ 0x0d, 0x00, 0x00
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_etc_cond2(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xff, 0x44, 0x44, 0xc0,
+ 0x00
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_etc_cond3(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xe1, 0x10, 0x1c, 0x17, 0x08, 0x1d
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_etc_cond4(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xe2, 0xed, 0x07, 0xc3, 0x13, 0x0d, 0x03
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_etc_cond5(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xf4, 0xcf, 0x0a, 0x12, 0x10, 0x19, 0x33, 0x02
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+static void s6e8ax0_etc_cond6(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xe3, 0x40
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_etc_cond7(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xe4, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_elvss_set(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xb1, 0x04, 0x00
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_elvss_nvm_set(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xd9, 0x5c, 0x20, 0x0c, 0x0f, 0x41, 0x00, 0x10, 0x11,
+ 0x12, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x80, 0xcb, 0xed,
+ 0x64, 0xaf
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_sleep_in(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0x10
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_sleep_out(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0x11
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_display_on(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0x29
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_display_off(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0x28
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_apply_level2_key(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xf0, 0x5a, 0x5a
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_acl_on(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xc0, 0x01
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+static void s6e8ax0_acl_off(struct s6e8ax0 *lcd)
+{
+ static const unsigned char data_to_send[] = {
+ 0xc0, 0x00
+ };
+
+ dsi_dcs_write(lcd->entity.source, 0,
+ data_to_send, ARRAY_SIZE(data_to_send));
+}
+
+/* Full white 50% reducing setting */
+static void s6e8ax0_acl_ctrl_set(struct s6e8ax0 *lcd)
+{
+ /* Full white 50% reducing setting */
+ static const unsigned char cutoff_50[] = {
+ 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf,
+ 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2a, 0x31, 0x38,
+ 0x3f, 0x46
+ };
+ /* Full white 45% reducing setting */
+ static const unsigned char cutoff_45[] = {
+ 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf,
+ 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x07, 0x0d, 0x13, 0x19, 0x1f, 0x25, 0x2b, 0x31,
+ 0x37, 0x3d
+ };
+ /* Full white 40% reducing setting */
+ static const unsigned char cutoff_40[] = {
+ 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf,
+ 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x06, 0x0c, 0x11, 0x16, 0x1c, 0x21, 0x26, 0x2b,
+ 0x31, 0x36
+ };
+
+ if (lcd->acl_enable) {
+ if (lcd->cur_acl = 0) {
+ if (lcd->gamma = 0 || lcd->gamma = 1) {
+ s6e8ax0_acl_off(lcd);
+ dev_dbg(lcd->dev,
+ "cur_acl=%d\n", lcd->cur_acl);
+ } else
+ s6e8ax0_acl_on(lcd);
+ }
+ switch (lcd->gamma) {
+ case 0: /* 30cd */
+ s6e8ax0_acl_off(lcd);
+ lcd->cur_acl = 0;
+ break;
+ case 1 ... 3: /* 50cd ~ 90cd */
+ dsi_dcs_write(lcd->entity.source, 0,
+ cutoff_40,
+ ARRAY_SIZE(cutoff_40));
+ lcd->cur_acl = 40;
+ break;
+ case 4 ... 7: /* 120cd ~ 210cd */
+ dsi_dcs_write(lcd->entity.source, 0,
+ cutoff_45,
+ ARRAY_SIZE(cutoff_45));
+ lcd->cur_acl = 45;
+ break;
+ case 8 ... 10: /* 220cd ~ 300cd */
+ dsi_dcs_write(lcd->entity.source, 0,
+ cutoff_50,
+ ARRAY_SIZE(cutoff_50));
+ lcd->cur_acl = 50;
+ break;
+ default:
+ break;
+ }
+ } else {
+ s6e8ax0_acl_off(lcd);
+ lcd->cur_acl = 0;
+ dev_dbg(lcd->dev, "cur_acl = %d\n", lcd->cur_acl);
+ }
+}
+
+static void s6e8ax0_read_id(struct s6e8ax0 *lcd, u8 *mtp_id)
+{
+ unsigned int ret;
+ u8 addr = 0xd1; /* MTP ID */
+
+ ret = dsi_dcs_read(lcd->entity.source, 0, addr, mtp_id, 3);
+}
+
+static int s6e8ax0_panel_init(struct s6e8ax0 *lcd)
+{
+ s6e8ax0_apply_level2_key(lcd);
+ s6e8ax0_sleep_out(lcd);
+ msleep(1);
+ s6e8ax0_panel_cond(lcd);
+ s6e8ax0_display_cond(lcd);
+ s6e8ax0_gamma_cond(lcd);
+ s6e8ax0_gamma_update(lcd);
+
+ s6e8ax0_etc_cond1(lcd);
+ s6e8ax0_etc_cond2(lcd);
+ s6e8ax0_etc_cond3(lcd);
+ s6e8ax0_etc_cond4(lcd);
+ s6e8ax0_etc_cond5(lcd);
+ s6e8ax0_etc_cond6(lcd);
+ s6e8ax0_etc_cond7(lcd);
+
+ s6e8ax0_elvss_nvm_set(lcd);
+ s6e8ax0_elvss_set(lcd);
+
+ s6e8ax0_acl_ctrl_set(lcd);
+ s6e8ax0_acl_on(lcd);
+
+ /* if ID3 value is not 33h, branch private elvss mode */
+ msleep(lcd->pdata->power_on_delay);
+
+ return 0;
+}
+
+static int s6e8ax0_update_gamma_ctrl(struct s6e8ax0 *lcd, int brightness)
+{
+ dsi_dcs_write(lcd->entity.source, 0,
+ s6e8ax0_22_gamma_table[brightness],
+ ARRAY_SIZE(s6e8ax0_22_gamma_table));
+
+ /* update gamma table. */
+ s6e8ax0_gamma_update(lcd);
+ lcd->gamma = brightness;
+
+ return 0;
+}
+
+static int s6e8ax0_gamma_ctrl(struct s6e8ax0 *lcd, int gamma)
+{
+ s6e8ax0_update_gamma_ctrl(lcd, gamma);
+
+ return 0;
+}
+
+static int s6e8ax0_get_brightness(struct backlight_device *bd)
+{
+ return bd->props.brightness;
+}
+
+static int s6e8ax0_set_brightness(struct backlight_device *bd)
+{
+ int ret = 0, brightness = bd->props.brightness;
+ struct s6e8ax0 *lcd = bl_get_data(bd);
+
+ if (brightness < MIN_BRIGHTNESS ||
+ brightness > bd->props.max_brightness) {
+ dev_err(lcd->dev,
+ "lcd brightness should be %d to %d.\n",
+ MIN_BRIGHTNESS, MAX_BRIGHTNESS);
+ return -EINVAL;
+ }
+
+ ret = s6e8ax0_gamma_ctrl(lcd, brightness);
+ if (ret) {
+ dev_err(lcd->dev,
+ "lcd brightness setting failed.\n");
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static const struct backlight_ops s6e8ax0_backlight_ops = {
+ .get_brightness = s6e8ax0_get_brightness,
+ .update_status = s6e8ax0_set_brightness,
+};
+
+static int s6e8ax0_set_power(struct lcd_device *ld, int power)
+{
+ struct s6e8ax0 *lcd = lcd_get_data(ld);
+ enum display_entity_state state;
+ int ret;
+
+ if (power = FB_BLANK_POWERDOWN)
+ state = DISPLAY_ENTITY_STATE_OFF;
+ else if (power != FB_BLANK_UNBLANK)
+ state = DISPLAY_ENTITY_STATE_STANDBY;
+ else
+ state = DISPLAY_ENTITY_STATE_ON;
+
+ ret = display_entity_set_state(&lcd->entity, state);
+ if (ret)
+ return ret;
+
+ lcd->power = power;
+ return 0;
+}
+
+static int s6e8ax0_get_power(struct lcd_device *ld)
+{
+ struct s6e8ax0 *lcd = lcd_get_data(ld);
+
+ return lcd->power;
+}
+
+static struct lcd_ops s6e8ax0_lcd_ops = {
+ .set_power = s6e8ax0_set_power,
+ .get_power = s6e8ax0_get_power,
+};
+
+static void s6e8ax0_set_sequence(struct s6e8ax0 *lcd)
+{
+ u8 mtp_id[3] = {0, };
+
+ msleep(100);
+
+ s6e8ax0_read_id(lcd, mtp_id);
+ if (mtp_id[0] = 0x00)
+ dev_err(lcd->dev, "read id failed\n");
+
+ dev_info(lcd->dev, "Read ID : %x, %x, %x\n",
+ mtp_id[0], mtp_id[1], mtp_id[2]);
+
+ if (mtp_id[2] = 0x33)
+ dev_info(lcd->dev,
+ "ID-3 is 0xff does not support dynamic elvss\n");
+ else
+ dev_info(lcd->dev,
+ "ID-3 is 0x%x support dynamic elvss\n", mtp_id[2]);
+
+ s6e8ax0_panel_init(lcd);
+ s6e8ax0_display_on(lcd);
+}
+
+#ifdef CONFIG_OF
+static int s6e8ax0_generic_reset(struct device *dev)
+{
+ struct s6e8ax0 *lcd = dev_get_drvdata(dev);
+
+ mdelay(10);
+ gpio_set_value(lcd->reset_gpio, 0);
+ mdelay(10);
+ gpio_set_value(lcd->reset_gpio, 1);
+
+ return 0;
+}
+
+static struct s6e8ax0_platform_data *s6e8ax0_parse_dt(struct s6e8ax0 *lcd)
+{
+ struct device_node *node = lcd->dev->of_node;
+ struct s6e8ax0_platform_data *data;
+ const __be32 *prop_data;
+ int ret;
+
+ data = devm_kzalloc(lcd->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ dev_err(lcd->dev, "failed to allocate platform data.\n");
+ return NULL;
+ }
+
+ ret = of_get_videomode(node, &data->mode, 0);
+ if (ret) {
+ dev_err(lcd->dev, "failed to read video mode from DT\n");
+ return NULL;
+ }
+
+ lcd->reset_gpio = of_get_named_gpio(node, "reset-gpio", 0);
+ if (lcd->reset_gpio < 0)
+ return NULL;
+
+ prop_data = of_get_property(node, "reset-delay", NULL);
+ if (!prop_data)
+ return NULL;
+ data->reset_delay = be32_to_cpu(*prop_data);
+
+ prop_data = of_get_property(node, "power-off-delay", NULL);
+ if (!prop_data)
+ return NULL;
+ data->power_off_delay = be32_to_cpu(*prop_data);
+
+ prop_data = of_get_property(node, "power-on-delay", NULL);
+ if (!prop_data)
+ return NULL;
+ data->power_on_delay = be32_to_cpu(*prop_data);
+
+ data->reset = s6e8ax0_generic_reset;
+
+ return data;
+}
+
+static struct of_device_id s6e8ax0_of_match[] = {
+ { .compatible = "samsung,s6e8ax0" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, s6e8ax0_of_match);
+#else
+static struct s6e8ax0_platform_data *s6e8ax0_parse_dt(struct s6e8ax0 *lcd)
+{
+ return NULL;
+}
+#endif
+
+static const struct display_entity_interface_params s6e8ax0_params = {
+ .type = DISPLAY_ENTITY_INTERFACE_DSI,
+ .p.dsi = {
+ .format = DSI_FMT_RGB888,
+ .mode = DSI_MODE_VIDEO | DSI_MODE_VIDEO_BURST
+ | DSI_MODE_VIDEO_HFP | DSI_MODE_VIDEO_HBP
+ | DSI_MODE_VIDEO_HSA | DSI_MODE_EOT_PACKET
+ | DSI_MODE_VSYNC_FLUSH,
+ .data_lanes = 0xf,
+ .hs_clk_freq = 500000000,
+ .esc_clk_freq = 10000000,
+ },
+};
+
+static int s6e8ax0_set_state(struct display_entity *entity,
+ enum display_entity_state state)
+{
+ struct s6e8ax0 *panel = to_panel(entity);
+
+ switch (state) {
+ case DISPLAY_ENTITY_STATE_OFF:
+ if (entity->state = DISPLAY_ENTITY_STATE_ON) {
+ s6e8ax0_sleep_in(panel);
+ panel->entity.source->common_ops->set_stream(entity->source,
+ DISPLAY_ENTITY_STREAM_STOPPED);
+
+ msleep(panel->pdata->power_off_delay);
+ s6e8ax0_display_off(panel);
+
+ panel->entity.source->ops.dsi->disable(entity->source);
+
+ regulator_bulk_disable(ARRAY_SIZE(panel->supplies),
+ panel->supplies);
+ } else if (entity->state = DISPLAY_ENTITY_STATE_STANDBY) {
+ msleep(panel->pdata->power_off_delay);
+ s6e8ax0_display_off(panel);
+
+ panel->entity.source->ops.dsi->disable(entity->source);
+
+ regulator_bulk_disable(ARRAY_SIZE(panel->supplies),
+ panel->supplies);
+ }
+ break;
+
+ case DISPLAY_ENTITY_STATE_STANDBY:
+ if (entity->state = DISPLAY_ENTITY_STATE_ON) {
+ s6e8ax0_sleep_in(panel);
+ panel->entity.source->common_ops->set_stream(entity->source,
+ DISPLAY_ENTITY_STREAM_STOPPED);
+ } else if (entity->state = DISPLAY_ENTITY_STATE_OFF) {
+ msleep(panel->pdata->power_on_delay);
+
+ regulator_bulk_enable(ARRAY_SIZE(panel->supplies),
+ panel->supplies);
+
+ msleep(panel->pdata->reset_delay);
+
+ /* lcd reset */
+ if (panel->pdata->reset)
+ panel->pdata->reset(panel->dev);
+ msleep(5);
+
+ panel->entity.source->ops.dsi->enable(entity->source);
+
+ s6e8ax0_set_sequence(panel);
+ s6e8ax0_sleep_in(panel);
+ }
+ break;
+
+ case DISPLAY_ENTITY_STATE_ON:
+ if (entity->state = DISPLAY_ENTITY_STATE_OFF) {
+ msleep(panel->pdata->power_on_delay);
+
+ regulator_bulk_enable(ARRAY_SIZE(panel->supplies),
+ panel->supplies);
+
+ msleep(panel->pdata->reset_delay);
+
+ /* lcd reset */
+ if (panel->pdata->reset)
+ panel->pdata->reset(panel->dev);
+ msleep(5);
+
+ panel->entity.source->ops.dsi->enable(entity->source);
+
+ s6e8ax0_set_sequence(panel);
+
+ panel->entity.source->common_ops->set_stream(entity->source,
+ DISPLAY_ENTITY_STREAM_CONTINUOUS);
+ } else {
+ panel->entity.source->common_ops->set_stream(entity->source,
+ DISPLAY_ENTITY_STREAM_CONTINUOUS);
+ s6e8ax0_sleep_out(panel);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int s6e8ax0_get_modes(struct display_entity *entity,
+ const struct videomode **modes)
+{
+ struct s6e8ax0 *panel = to_panel(entity);
+
+ *modes = &panel->pdata->mode;
+ return 1;
+}
+
+static int s6e8ax0_get_size(struct display_entity *entity,
+ unsigned int *width, unsigned int *height)
+{
+ struct s6e8ax0 *panel = to_panel(entity);
+
+ *width = panel->pdata->width;
+ *height = panel->pdata->height;
+ return 0;
+}
+
+static int s6e8ax0_get_params(struct display_entity *entity,
+ struct display_entity_interface_params *params)
+{
+ *params = s6e8ax0_params;
+ return 0;
+}
+
+static const struct display_entity_control_ops s6e8ax0_control_ops = {
+ .set_state = s6e8ax0_set_state,
+ .get_modes = s6e8ax0_get_modes,
+ .get_size = s6e8ax0_get_size,
+ .get_params = s6e8ax0_get_params,
+};
+
+static void s6e8ax0_release(struct display_entity *entity)
+{
+ struct s6e8ax0 *panel = to_panel(entity);
+
+ backlight_device_unregister(panel->bd);
+ lcd_device_unregister(panel->ld);
+ regulator_bulk_free(ARRAY_SIZE(panel->supplies), panel->supplies);
+ kfree(panel);
+}
+
+static int s6e8ax0_probe(struct platform_device *pdev)
+{
+ struct s6e8ax0 *lcd;
+ int ret;
+
+ lcd = kzalloc(sizeof(struct s6e8ax0), GFP_KERNEL);
+ if (!lcd) {
+ dev_err(&pdev->dev, "failed to allocate s6e8ax0 structure.\n");
+ return -ENOMEM;
+ }
+
+ lcd->dev = &pdev->dev;
+ lcd->pdata = (struct s6e8ax0_platform_data *)pdev->dev.platform_data;
+
+ if (!lcd->pdata) {
+ lcd->pdata = s6e8ax0_parse_dt(lcd);
+ if (!lcd->pdata) {
+ dev_err(&pdev->dev, "failed to find platform data\n");
+ return -ENODEV;
+ }
+ }
+
+ lcd->supplies[0].supply = "vdd3";
+ lcd->supplies[1].supply = "vci";
+ ret = regulator_bulk_get(&pdev->dev,
+ ARRAY_SIZE(lcd->supplies), lcd->supplies);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret);
+ goto err_regulator_bulk_get;
+ }
+
+ lcd->ld = lcd_device_register("s6e8ax0", &pdev->dev, lcd,
+ &s6e8ax0_lcd_ops);
+ if (IS_ERR(lcd->ld)) {
+ dev_err(&pdev->dev, "failed to register lcd ops.\n");
+ ret = PTR_ERR(lcd->ld);
+ goto err_lcd_register;
+ }
+
+ lcd->bd = backlight_device_register("s6e8ax0-bl", &pdev->dev, lcd,
+ &s6e8ax0_backlight_ops, NULL);
+ if (IS_ERR(lcd->bd)) {
+ dev_err(&pdev->dev, "failed to register backlight ops.\n");
+ ret = PTR_ERR(lcd->bd);
+ goto err_backlight_register;
+ }
+
+ lcd->acl_enable = 1;
+ lcd->cur_acl = 0;
+ lcd->power = FB_BLANK_UNBLANK;
+
+ lcd->bd->props.max_brightness = MAX_BRIGHTNESS;
+ lcd->bd->props.brightness = MAX_BRIGHTNESS;
+
+ lcd->entity.of_node = pdev->dev.of_node;
+ lcd->entity.dev = &pdev->dev;
+ lcd->entity.release = s6e8ax0_release;
+ lcd->entity.ops = &s6e8ax0_control_ops;
+
+ platform_set_drvdata(pdev, lcd);
+
+ ret = display_entity_register(&lcd->entity);
+ if (ret < 0)
+ goto err_display_register;
+
+ display_entity_set_state(&lcd->entity, DISPLAY_ENTITY_STATE_ON);
+
+ dev_dbg(&pdev->dev, "probed s6e8ax0 panel driver.\n");
+
+ return 0;
+
+err_display_register:
+ backlight_device_unregister(lcd->bd);
+err_backlight_register:
+ lcd_device_unregister(lcd->ld);
+err_lcd_register:
+ regulator_bulk_free(ARRAY_SIZE(lcd->supplies), lcd->supplies);
+err_regulator_bulk_get:
+ kfree(lcd);
+
+ return ret;
+}
+
+static int s6e8ax0_remove(struct platform_device *dev)
+{
+ struct s6e8ax0 *lcd = platform_get_drvdata(dev);
+
+ platform_set_drvdata(dev, NULL);
+ display_entity_unregister(&lcd->entity);
+
+ return 0;
+}
+
+static int s6e8ax0_suspend(struct device *dev)
+{
+ struct s6e8ax0 *lcd = dev_get_drvdata(dev);
+
+ if (lcd->power = FB_BLANK_UNBLANK)
+ display_entity_set_state(&lcd->entity,
+ DISPLAY_ENTITY_STATE_OFF);
+
+ return 0;
+}
+
+static int s6e8ax0_resume(struct device *dev)
+{
+ struct s6e8ax0 *lcd = dev_get_drvdata(dev);
+
+ if (lcd->power = FB_BLANK_UNBLANK)
+ display_entity_set_state(&lcd->entity,
+ DISPLAY_ENTITY_STATE_ON);
+
+ return 0;
+}
+
+static struct dev_pm_ops s6e8ax0_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(s6e8ax0_suspend, s6e8ax0_resume)
+};
+
+static struct platform_driver s6e8ax0_driver = {
+ .probe = s6e8ax0_probe,
+ .remove = s6e8ax0_remove,
+ .driver = {
+ .name = "panel_s6e8ax0",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(s6e8ax0_of_match),
+ .pm = &s6e8ax0_pm_ops,
+ },
+};
+module_platform_driver(s6e8ax0_driver);
+
+MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
+MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
+MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>");
+MODULE_DESCRIPTION("MIPI-DSI based s6e8ax0 AMOLED LCD Panel Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel-s6e8ax0.h b/include/video/panel-s6e8ax0.h
new file mode 100644
index 0000000..e522bfb
--- /dev/null
+++ b/include/video/panel-s6e8ax0.h
@@ -0,0 +1,41 @@
+/*
+ * Renesas R61505-based Display Panels
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.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.
+ */
+
+#ifndef __PANEL_S6E8AX0_H__
+#define __PANEL_S6E8AX0_H__
+
+#include <linux/videomode.h>
+
+struct s6e8ax0_platform_data {
+ unsigned long width; /* Panel width in mm */
+ unsigned long height; /* Panel height in mm */
+ struct videomode mode;
+
+ /* reset lcd panel device. */
+ int (*reset)(struct device *dev);
+
+ /* it indicates whether lcd panel was enabled
+ from bootloader or not. */
+ int lcd_enabled;
+ /* it means delay for stable time when it becomes low to high
+ or high to low that is dependent on whether reset gpio is
+ low active or high active. */
+ unsigned int reset_delay;
+ /* stable time needing to become lcd power on. */
+ unsigned int power_on_delay;
+ /* stable time needing to become lcd power off. */
+ unsigned int power_off_delay;
+ /* panel is reversed */
+ bool panel_reverse;
+};
+
+#endif /* __PANEL_S6E8AX0_H__ */
--
1.8.1
^ permalink raw reply related
* [RFC PATCH 3/4] video: display: Add exynos-dsi video source driver
From: Tomasz Figa @ 2013-01-30 15:39 UTC (permalink / raw)
To: dri-devel
Cc: linux-fbdev, linux-samsung-soc, kyungmin.park, m.szyprowski,
t.figa, tomasz.figa, Daniel Vetter, Marcus Lorentzon,
Laurent Pinchart, rob, tomi.valkeinen, Vikas Sajjan, inki.dae,
dh09.lee, ville.syrjala, s.nawrocki
In-Reply-To: <1359560343-31636-1-git-send-email-t.figa@samsung.com>
This patch adds new driver for DSI master block available in Samsung
Exynos SoCs. The driver is designed and written specifically for Common
Display Framework.
Signed-off-by: Tomasz Figa <t.figa@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/video/display/Kconfig | 3 +
drivers/video/display/Makefile | 1 +
drivers/video/display/source-exynos_dsi.c | 1313 +++++++++++++++++++++++++++++
include/video/exynos_dsi.h | 41 +
4 files changed, 1358 insertions(+)
create mode 100644 drivers/video/display/source-exynos_dsi.c
create mode 100644 include/video/exynos_dsi.h
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig
index 477192d..b14527a 100644
--- a/drivers/video/display/Kconfig
+++ b/drivers/video/display/Kconfig
@@ -6,5 +6,8 @@ menuconfig DISPLAY_CORE
if DISPLAY_CORE
+config DISPLAY_SOURCE_EXYNOS_DSI
+ tristate "Samsung SoC MIPI DSI Master"
+
endif # DISPLAY_CORE
diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile
index bd93496..40a283a 100644
--- a/drivers/video/display/Makefile
+++ b/drivers/video/display/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_DISPLAY_CORE) += display-core.o
+obj-$(CONFIG_DISPLAY_SOURCE_EXYNOS_DSI) += source-exynos_dsi.o
diff --git a/drivers/video/display/source-exynos_dsi.c b/drivers/video/display/source-exynos_dsi.c
new file mode 100644
index 0000000..30b15bf
--- /dev/null
+++ b/drivers/video/display/source-exynos_dsi.c
@@ -0,0 +1,1313 @@
+/*
+ * Samsung SoC MIPI DSI Master driver.
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd
+ *
+ * Contacts: Tomasz Figa <t.figa@samsung.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.
+*/
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/memory.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spinlock.h>
+#include <linux/videomode.h>
+#include <linux/wait.h>
+
+#include <video/display.h>
+#include <video/exynos_dsi.h>
+#include <video/mipi_display.h>
+
+#define DSIM_STATUS_REG 0x0 /* Status register */
+#define DSIM_SWRST_REG 0x4 /* Software reset register */
+#define DSIM_CLKCTRL_REG 0x8 /* Clock control register */
+#define DSIM_TIMEOUT_REG 0xc /* Time out register */
+#define DSIM_CONFIG_REG 0x10 /* Configuration register */
+#define DSIM_ESCMODE_REG 0x14 /* Escape mode register */
+
+/* Main display image resolution register */
+#define DSIM_MDRESOL_REG 0x18
+#define DSIM_MVPORCH_REG 0x1c /* Main display Vporch register */
+#define DSIM_MHPORCH_REG 0x20 /* Main display Hporch register */
+#define DSIM_MSYNC_REG 0x24 /* Main display sync area register */
+
+/* Sub display image resolution register */
+#define DSIM_SDRESOL_REG 0x28
+#define DSIM_INTSRC_REG 0x2c /* Interrupt source register */
+#define DSIM_INTMSK_REG 0x30 /* Interrupt mask register */
+#define DSIM_PKTHDR_REG 0x34 /* Packet Header FIFO register */
+#define DSIM_PAYLOAD_REG 0x38 /* Payload FIFO register */
+#define DSIM_RXFIFO_REG 0x3c /* Read FIFO register */
+#define DSIM_FIFOTHLD_REG 0x40 /* FIFO threshold level register */
+#define DSIM_FIFOCTRL_REG 0x44 /* FIFO status and control register */
+
+/* FIFO memory AC characteristic register */
+#define DSIM_PLLCTRL_REG 0x4c /* PLL control register */
+#define DSIM_PLLTMR_REG 0x50 /* PLL timer register */
+#define DSIM_PHYACCHR_REG 0x54 /* D-PHY AC characteristic register */
+#define DSIM_PHYACCHR1_REG 0x58 /* D-PHY AC characteristic register1 */
+
+/* DSIM_STATUS */
+#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0)
+#define DSIM_STOP_STATE_CLK (1 << 8)
+#define DSIM_TX_READY_HS_CLK (1 << 10)
+#define DSIM_PLL_STABLE (1 << 31)
+
+/* DSIM_SWRST */
+#define DSIM_FUNCRST (1 << 16)
+#define DSIM_SWRST (1 << 0)
+
+/* DSIM_TIMEOUT */
+#define DSIM_LPDR_TOUT(x) ((x) << 0)
+#define DSIM_BTA_TOUT(x) ((x) << 16)
+
+/* DSIM_CLKCTRL */
+#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0)
+#define DSIM_ESC_PRESCALER_MASK (0xffff << 0)
+#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19)
+#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20)
+#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20)
+#define DSIM_BYTE_CLKEN (1 << 24)
+#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25)
+#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25)
+#define DSIM_PLL_BYPASS (1 << 27)
+#define DSIM_ESC_CLKEN (1 << 28)
+#define DSIM_TX_REQUEST_HSCLK (1 << 31)
+
+/* DSIM_CONFIG */
+#define DSIM_LANE_EN_CLK (1 << 0)
+#define DSIM_LANE_EN(x) (((x) & 0xf) << 1)
+#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5)
+#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8)
+#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12)
+#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12)
+#define DSIM_SUB_VC (((x) & 0x3) << 16)
+#define DSIM_MAIN_VC (((x) & 0x3) << 18)
+#define DSIM_HSA_MODE (1 << 20)
+#define DSIM_HBP_MODE (1 << 21)
+#define DSIM_HFP_MODE (1 << 22)
+#define DSIM_HSE_MODE (1 << 23)
+#define DSIM_AUTO_MODE (1 << 24)
+#define DSIM_VIDEO_MODE (1 << 25)
+#define DSIM_BURST_MODE (1 << 26)
+#define DSIM_SYNC_INFORM (1 << 27)
+#define DSIM_EOT_DISABLE (1 << 28)
+#define DSIM_MFLUSH_VS (1 << 29)
+
+/* DSIM_ESCMODE */
+#define DSIM_TX_LPDT_LP (1 << 6)
+#define DSIM_CMD_LPDT_LP (1 << 7)
+#define DSIM_FORCE_STOP_STATE (1 << 20)
+#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21)
+#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
+
+/* DSIM_MDRESOL */
+#define DSIM_MAIN_STAND_BY (1 << 31)
+#define DSIM_MAIN_VRESOL(x) (((x) & 0x7ff) << 16)
+#define DSIM_MAIN_HRESOL(x) (((x) & 0X7ff) << 0)
+
+/* DSIM_MVPORCH */
+#define DSIM_CMD_ALLOW(x) ((x) << 28)
+#define DSIM_STABLE_VFP(x) ((x) << 16)
+#define DSIM_MAIN_VBP(x) ((x) << 0)
+#define DSIM_CMD_ALLOW_MASK (0xf << 28)
+#define DSIM_STABLE_VFP_MASK (0x7ff << 16)
+#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
+
+/* DSIM_MHPORCH */
+#define DSIM_MAIN_HFP(x) ((x) << 16)
+#define DSIM_MAIN_HBP(x) ((x) << 0)
+#define DSIM_MAIN_HFP_MASK ((0xffff) << 16)
+#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
+
+/* DSIM_MSYNC */
+#define DSIM_MAIN_VSA(x) ((x) << 22)
+#define DSIM_MAIN_HSA(x) ((x) << 0)
+#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22)
+#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
+
+/* DSIM_SDRESOL */
+#define DSIM_SUB_STANDY(x) ((x) << 31)
+#define DSIM_SUB_VRESOL(x) ((x) << 16)
+#define DSIM_SUB_HRESOL(x) ((x) << 0)
+#define DSIM_SUB_STANDY_MASK ((0x1) << 31)
+#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16)
+#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
+
+/* DSIM_INTSRC */
+#define DSIM_INT_PLL_STABLE (1 << 31)
+#define DSIM_INT_SW_RST_RELEASE (1 << 30)
+#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29)
+#define DSIM_INT_BTA (1 << 25)
+#define DSIM_INT_FRAME_DONE (1 << 24)
+#define DSIM_INT_RX_TIMEOUT (1 << 21)
+#define DSIM_INT_BTA_TIMEOUT (1 << 20)
+#define DSIM_INT_RX_DONE (1 << 18)
+#define DSIM_INT_RX_TE (1 << 17)
+#define DSIM_INT_RX_ACK (1 << 16)
+#define DSIM_INT_RX_ECC_ERR (1 << 15)
+#define DSIM_INT_RX_CRC_ERR (1 << 14)
+
+/* DSIM_FIFOCTRL */
+#define SFR_HEADER_EMPTY (1 << 22)
+
+/* DSIM_PHYACCHR */
+#define DSIM_AFC_EN (1 << 14)
+#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
+
+/* DSIM_PLLCTRL */
+#define DSIM_FREQ_BAND(x) ((x) << 24)
+#define DSIM_PLL_EN (1 << 23)
+#define DSIM_PLL_P(x) ((x) << 13)
+#define DSIM_PLL_M(x) ((x) << 4)
+#define DSIM_PLL_S(x) ((x) << 1)
+
+#define DSI_MAX_BUS_WIDTH 4
+#define DSI_NUM_VIRTUAL_CHANNELS 4
+#define DSI_TX_FIFO_SIZE 2048
+#define DSI_XFER_TIMEOUT_MS 100
+
+enum exynos_dsi_transfer_type {
+ EXYNOS_DSI_TX,
+ EXYNOS_DSI_RX,
+};
+
+struct exynos_dsi_transfer {
+ struct list_head list;
+ struct completion completed;
+ int result;
+ u8 type;
+ u8 data[2];
+
+ const u8 *tx_payload;
+ u16 tx_len;
+ u16 tx_done;
+
+ u8 *rx_payload;
+ u16 rx_len;
+ u16 rx_done;
+};
+
+struct exynos_dsi {
+ struct video_source out;
+ struct mipi_dsi_interface_params params;
+ bool streaming;
+
+ struct device *dev;
+ struct resource *res;
+ struct clk *pll_clk;
+ struct clk *bus_clk;
+ unsigned int irq;
+ void __iomem *reg_base;
+ struct regulator_bulk_data supplies[2];
+ struct exynos_dsi_platform_data *pd;
+
+ spinlock_t transfer_lock;
+ struct list_head transfer_list;
+ bool hs_mode;
+};
+
+#define src_to_dsi(src) container_of(src, struct exynos_dsi, out)
+
+/*
+ * H/W control
+ */
+
+static void exynos_dsi_reset(struct exynos_dsi *dsi)
+{
+ writel(DSIM_SWRST, dsi->reg_base + DSIM_SWRST_REG);
+
+ udelay(10);
+}
+
+#ifndef MHZ
+#define MHZ (1000*1000)
+#endif
+
+static const unsigned long freq_bands[] = {
+ 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ,
+ 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ,
+ 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ,
+ 770 * MHZ, 870 * MHZ, 950 * MHZ,
+};
+
+static const int afc_settings[] = {
+ 1, 0, 3, 2, 5, 4,
+};
+
+static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi,
+ unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s)
+{
+ unsigned long best_freq = 0;
+ u32 min_delta = 0xffffffff;
+ u8 p_min, p_max;
+ u8 _p, best_p;
+ u16 _m, best_m;
+ u8 _s, best_s;
+
+ p_min = DIV_ROUND_UP(fin, (12 * MHZ));
+ p_max = fin / (6 * MHZ);
+
+ for (_p = p_min; _p <= p_max; ++_p) {
+ for (_s = 0; _s <= 5; ++_s) {
+ u64 tmp;
+ u32 delta;
+ u16 div;
+
+ tmp = (u64)fout * (_p << _s);
+ do_div(tmp, fin);
+ _m = tmp;
+ if (_m < 41 || _m > 125)
+ continue;
+
+ tmp = (u64)_m * fin;
+ do_div(tmp, _p);
+ if (tmp < 500 * MHZ || tmp > 1000 * MHZ)
+ continue;
+
+ tmp = (u64)_m * fin;
+ div = (_p << _s);
+ do_div(tmp, div);
+
+ delta = abs(fout - tmp);
+ if (delta < min_delta) {
+ best_p = _p;
+ best_m = _m;
+ best_s = _s;
+ min_delta = delta;
+ best_freq = tmp;
+ }
+ }
+ }
+
+ if (best_freq) {
+ *p = best_p;
+ *m = best_m;
+ *s = best_s;
+ }
+
+ return best_freq;
+}
+
+static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi,
+ unsigned long freq)
+{
+ unsigned long fin, fout, fin_pll;
+ int timeout;
+ u8 p;
+ u16 m;
+ u8 s;
+ int band;
+ int afc;
+ u32 reg;
+
+ clk_set_rate(dsi->pll_clk, dsi->pd->pll_clk_rate);
+
+ fin = clk_get_rate(dsi->pll_clk);
+ if (!fin) {
+ dev_err(dsi->dev, "failed to get PLL clock frequency\n");
+ return 0;
+ }
+
+ dev_dbg(dsi->dev, "PLL input frequency: %lu\n", fin);
+
+ fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s);
+ if (!fout) {
+ dev_err(dsi->dev,
+ "failed to find PLL coefficients for requested frequency\n");
+ return -EFAULT;
+ }
+
+ dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
+
+ for (band = 0; band < ARRAY_SIZE(freq_bands); ++band)
+ if (fout < freq_bands[band])
+ break;
+
+ fin_pll = DIV_ROUND_CLOSEST(fin, p);
+ fin_pll /= MHZ;
+ if (fin_pll > 6)
+ fin_pll -= 6;
+ else
+ fin_pll = 0;
+ if (fin_pll >= ARRAY_SIZE(afc_settings))
+ fin_pll = ARRAY_SIZE(afc_settings) - 1;
+
+ afc = afc_settings[fin_pll];
+
+ dev_dbg(dsi->dev, "freq band %d, afc_setting %d\n", band, afc);
+
+ writel(dsi->pd->pll_stable_time, dsi->reg_base + DSIM_PLLTMR_REG);
+
+ reg = DSIM_AFC_CTL(afc) | DSIM_AFC_EN;
+ writel(reg, dsi->reg_base + DSIM_PHYACCHR_REG);
+
+ reg = DSIM_FREQ_BAND(band) | DSIM_PLL_EN
+ | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s);
+ writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG);
+
+ timeout = 1000;
+ do {
+ if (timeout-- = 0) {
+ dev_err(dsi->dev, "PLL failed to stabilize\n");
+ return -EFAULT;
+ }
+ reg = readl(dsi->reg_base + DSIM_STATUS_REG);
+ } while ((reg & DSIM_PLL_STABLE) = 0);
+
+ return fout;
+}
+
+static int exynos_dsi_enable_clock(struct exynos_dsi *dsi)
+{
+ unsigned long hs_clk, byte_clk, esc_clk;
+ unsigned long esc_div;
+ u32 reg;
+
+ hs_clk = exynos_dsi_set_pll(dsi, dsi->params.hs_clk_freq);
+ if (!hs_clk) {
+ dev_err(dsi->dev, "failed to configure DSI PLL\n");
+ return -EFAULT;
+ }
+
+ byte_clk = hs_clk / 8;
+ esc_div = DIV_ROUND_UP(byte_clk, dsi->params.esc_clk_freq);
+ esc_clk = byte_clk / esc_div;
+
+ if (esc_clk > 20 * MHZ) {
+ ++esc_div;
+ esc_clk = byte_clk / esc_div;
+ }
+
+ dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n",
+ hs_clk, byte_clk, esc_clk);
+
+ reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG);
+ reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK
+ | DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS
+ | DSIM_BYTE_CLK_SRC_MASK);
+ reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN
+ | DSIM_ESC_PRESCALER(esc_div)
+ | DSIM_LANE_ESC_CLK_EN_CLK
+ | DSIM_LANE_ESC_CLK_EN_DATA(dsi->params.data_lanes)
+ | DSIM_BYTE_CLK_SRC(0)
+ | DSIM_TX_REQUEST_HSCLK;
+ writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG);
+
+ return 0;
+}
+
+static void exynos_dsi_disable_clock(struct exynos_dsi *dsi)
+{
+ u32 reg;
+
+ reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG);
+ reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK |
+ DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
+ writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG);
+
+ reg = readl(dsi->reg_base + DSIM_PLLCTRL_REG);
+ reg &= ~DSIM_PLL_EN;
+ writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG);
+}
+
+static int exynos_dsi_init_link(struct exynos_dsi *dsi)
+{
+ u32 reg;
+ int timeout;
+
+ /* Initialize FIFO pointers */
+ reg = readl(dsi->reg_base + DSIM_FIFOCTRL_REG);
+ reg &= ~0x1f;
+ writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG);
+
+ msleep(10);
+
+ reg |= 0x1f;
+ writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG);
+
+ msleep(10);
+
+ /* DSI configuration */
+ reg = 0;
+
+ if (dsi->params.mode & DSI_MODE_VIDEO) {
+ reg |= DSIM_VIDEO_MODE;
+
+ if (!(dsi->params.mode & DSI_MODE_VSYNC_FLUSH))
+ reg |= DSIM_MFLUSH_VS;
+ if (!(dsi->params.mode & DSI_MODE_EOT_PACKET))
+ reg |= DSIM_EOT_DISABLE;
+ if (dsi->params.mode & DSI_MODE_VIDEO_SYNC_PULSE)
+ reg |= DSIM_SYNC_INFORM;
+ if (dsi->params.mode & DSI_MODE_VIDEO_BURST)
+ reg |= DSIM_BURST_MODE;
+ if (dsi->params.mode & DSI_MODE_VIDEO_AUTO_VERT)
+ reg |= DSIM_AUTO_MODE;
+ if (dsi->params.mode & DSI_MODE_VIDEO_HSE)
+ reg |= DSIM_HSE_MODE;
+ if (!(dsi->params.mode & DSI_MODE_VIDEO_HFP))
+ reg |= DSIM_HFP_MODE;
+ if (!(dsi->params.mode & DSI_MODE_VIDEO_HBP))
+ reg |= DSIM_HBP_MODE;
+ if (!(dsi->params.mode & DSI_MODE_VIDEO_HSA))
+ reg |= DSIM_HSA_MODE;
+ }
+
+ switch (dsi->params.format) {
+ case DSI_FMT_RGB888:
+ reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
+ break;
+ case DSI_FMT_RGB666:
+ reg |= DSIM_MAIN_PIX_FORMAT_RGB666;
+ break;
+ case DSI_FMT_RGB666_PACKED:
+ reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
+ break;
+ case DSI_FMT_RGB565:
+ reg |= DSIM_MAIN_PIX_FORMAT_RGB565;
+ break;
+ default:
+ dev_err(dsi->dev, "invalid pixel format\n");
+ return -EINVAL;
+ }
+
+ switch (dsi->params.data_lanes) {
+ case 0x1:
+ reg |= DSIM_NUM_OF_DATA_LANE(0);
+ break;
+ case 0x3:
+ reg |= DSIM_NUM_OF_DATA_LANE(1);
+ break;
+ case 0x7:
+ reg |= DSIM_NUM_OF_DATA_LANE(2);
+ break;
+ case 0xf:
+ reg |= DSIM_NUM_OF_DATA_LANE(3);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(reg, dsi->reg_base + DSIM_CONFIG_REG);
+
+ reg |= DSIM_LANE_EN_CLK;
+ writel(reg, dsi->reg_base + DSIM_CONFIG_REG);
+
+ reg |= DSIM_LANE_EN(dsi->params.data_lanes);
+ writel(reg, dsi->reg_base + DSIM_CONFIG_REG);
+
+ /* Clock configuration */
+ exynos_dsi_enable_clock(dsi);
+
+ /* Check clock and data lane state are stop state */
+ timeout = 100;
+ do {
+ if (timeout-- = 0) {
+ dev_err(dsi->dev, "waiting for bus lanes timed out\n");
+ return -EFAULT;
+ }
+
+ reg = readl(dsi->reg_base + DSIM_STATUS_REG);
+ if ((reg & DSIM_STOP_STATE_DAT(dsi->params.data_lanes))
+ != DSIM_STOP_STATE_DAT(dsi->params.data_lanes))
+ continue;
+ } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK)));
+
+ reg = readl(dsi->reg_base + DSIM_ESCMODE_REG);
+ reg &= ~DSIM_STOP_STATE_CNT_MASK;
+ reg |= DSIM_STOP_STATE_CNT(dsi->pd->stop_holding_cnt);
+ writel(reg, dsi->reg_base + DSIM_ESCMODE_REG);
+
+ reg = DSIM_BTA_TOUT(dsi->pd->bta_timeout)
+ | DSIM_LPDR_TOUT(dsi->pd->rx_timeout);
+ writel(reg, dsi->reg_base + DSIM_TIMEOUT_REG);
+
+ return 0;
+}
+
+static int exynos_dsi_set_display_mode(struct exynos_dsi *dsi)
+{
+ const struct videomode *mode;
+ u32 reg;
+ int ret;
+
+ ret = display_entity_get_modes(dsi->out.sink, &mode);
+ if (ret < 0) {
+ dev_err(dsi->dev, "failed to get display video mode\n");
+ return ret;
+ }
+
+ if (dsi->params.mode & DSI_MODE_VIDEO) {
+ reg = DSIM_CMD_ALLOW(dsi->params.cmd_allow)
+ | DSIM_STABLE_VFP(mode->vfront_porch)
+ | DSIM_MAIN_VBP(mode->vback_porch);
+ writel(reg, dsi->reg_base + DSIM_MVPORCH_REG);
+
+ reg = DSIM_MAIN_HFP(mode->hfront_porch)
+ | DSIM_MAIN_HBP(mode->hback_porch);
+ writel(reg, dsi->reg_base + DSIM_MHPORCH_REG);
+
+ reg = DSIM_MAIN_VSA(mode->vsync_len)
+ | DSIM_MAIN_HSA(mode->hsync_len);
+ writel(reg, dsi->reg_base + DSIM_MSYNC_REG);
+ }
+
+ reg = DSIM_MAIN_HRESOL(mode->hactive) | DSIM_MAIN_VRESOL(mode->vactive);
+ writel(reg, dsi->reg_base + DSIM_MDRESOL_REG);
+
+ dev_dbg(dsi->dev, "LCD width = %d, height = %d\n",
+ mode->hactive, mode->vactive);
+
+ return 0;
+}
+
+static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable)
+{
+ u32 reg;
+
+ reg = readl(dsi->reg_base + DSIM_MDRESOL_REG);
+ if (enable)
+ reg |= DSIM_MAIN_STAND_BY;
+ else
+ reg &= ~DSIM_MAIN_STAND_BY;
+ writel(reg, dsi->reg_base +DSIM_MDRESOL_REG);
+}
+
+/*
+ * FIFO
+ */
+
+static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi,
+ struct exynos_dsi_transfer *xfer)
+{
+ const u8 *payload = xfer->tx_payload + xfer->tx_done;
+ u16 length = xfer->tx_len - xfer->tx_done;
+ bool first = !xfer->tx_done;
+ u32 reg;
+
+ dev_dbg(dsi->dev,
+ "< xfer %p, tx_len %u, tx_done %u, rx_len %u, rx_done %u\n",
+ xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done);
+
+ if (length > DSI_TX_FIFO_SIZE)
+ length = DSI_TX_FIFO_SIZE;
+
+ xfer->tx_done += length;
+
+ /* Send payload */
+ while (length >= 4) {
+ reg = (payload[3] << 24) | (payload[2] << 16)
+ | (payload[1] << 8) | payload[0];
+ writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG);
+ payload += 4;
+ length -= 4;
+ }
+
+ reg = 0;
+ switch (length) {
+ case 3:
+ reg |= payload[2] << 16;
+ /* Fall through */
+ case 2:
+ reg |= payload[1] << 8;
+ /* Fall through */
+ case 1:
+ reg |= payload[0];
+ writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG);
+ break;
+ case 0:
+ /* Do nothing */
+ break;
+ }
+
+ /* Send packet header */
+ if (first) {
+ if (xfer->rx_len) {
+ reg = (xfer->rx_len << 8)
+ | MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE;
+ writel(reg, dsi->reg_base + DSIM_PKTHDR_REG);
+ }
+ reg = (xfer->data[1] << 16) | (xfer->data[0] << 8) | xfer->type;
+ writel(reg, dsi->reg_base + DSIM_PKTHDR_REG);
+ }
+}
+
+static void exynos_readl_from_fifo(struct exynos_dsi *dsi,
+ struct exynos_dsi_transfer *xfer)
+{
+ u8 *payload = xfer->rx_payload + xfer->rx_done;
+ bool first = !xfer->rx_done;
+ u16 length;
+ u32 reg;
+
+ if (first) {
+ reg = readl(dsi->reg_base + DSIM_RXFIFO_REG);
+ length = (reg >> 8) & 0xffff;
+ if (length > xfer->rx_len)
+ dev_dbg(dsi->dev,
+ "response too long (expected %u, got %u bytes)\n",
+ xfer->rx_len, length);
+ if (length < xfer->rx_len)
+ xfer->rx_len = length;
+ }
+
+ length = xfer->rx_len - xfer->rx_done;
+ xfer->rx_done += length;
+
+ /* Receive payload */
+ while (length >= 4) {
+ reg = readl(dsi->reg_base + DSIM_RXFIFO_REG);
+ payload[0] = (reg >> 0) & 0xff;
+ payload[1] = (reg >> 8) & 0xff;
+ payload[2] = (reg >> 16) & 0xff;
+ payload[3] = (reg >> 24) & 0xff;
+ payload += 4;
+ length -= 4;
+ }
+
+ if (length) {
+ reg = readl(dsi->reg_base + DSIM_RXFIFO_REG);
+ switch (length) {
+ case 3:
+ payload[2] = (reg >> 16) & 0xff;
+ /* Fall through */
+ case 2:
+ payload[1] = (reg >> 8) & 0xff;
+ /* Fall through */
+ case 1:
+ payload[0] = reg & 0xff;
+ }
+ }
+
+ do {
+ reg = readl(dsi->reg_base + DSIM_RXFIFO_REG);
+ } while (reg != 0x30800002);
+}
+
+/*
+ * Transfer
+ */
+
+static void exynos_dsi_transfer_cancel(struct exynos_dsi *dsi,
+ struct exynos_dsi_transfer *xfer)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dsi->transfer_lock, flags);
+
+ if (!list_empty(&xfer->list))
+ list_del_init(&xfer->list);
+
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+}
+
+static void exynos_dsi_transfer_start(struct exynos_dsi *dsi)
+{
+ unsigned long flags;
+ struct exynos_dsi_transfer *xfer;
+
+ spin_lock_irqsave(&dsi->transfer_lock, flags);
+
+ if (list_empty(&dsi->transfer_list)) {
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+ return;
+ }
+
+ xfer = list_first_entry(&dsi->transfer_list,
+ struct exynos_dsi_transfer, list);
+
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+
+ if (xfer->tx_len && xfer->tx_done = xfer->tx_len)
+ /* waiting for RX */
+ return;
+
+ exynos_dsi_send_to_fifo(dsi, xfer);
+}
+
+static void exynos_dsi_transfer_finish(struct exynos_dsi *dsi,
+ enum exynos_dsi_transfer_type type)
+{
+ struct exynos_dsi_transfer *xfer;
+ unsigned long flags;
+ bool start = true;
+
+ spin_lock_irqsave(&dsi->transfer_lock, flags);
+
+ if (list_empty(&dsi->transfer_list)) {
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+ dev_warn(dsi->dev, "unexpected TX/RX interrupt\n");
+ return;
+ }
+
+ xfer = list_first_entry(&dsi->transfer_list,
+ struct exynos_dsi_transfer, list);
+
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+
+ dev_dbg(dsi->dev,
+ "> xfer %p, tx_len %u, tx_done %u, rx_len %u, rx_done %u\n",
+ xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done);
+
+ if (type = EXYNOS_DSI_RX)
+ exynos_readl_from_fifo(dsi, xfer);
+
+ if (xfer->tx_done = xfer->tx_len && xfer->rx_done = xfer->rx_len) {
+ spin_lock_irqsave(&dsi->transfer_lock, flags);
+ list_del_init(&xfer->list);
+ start = !list_empty(&dsi->transfer_list);
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+ xfer->result = 0;
+ complete(&xfer->completed);
+ }
+
+ if (type = EXYNOS_DSI_TX && xfer->tx_done != xfer->tx_len)
+ exynos_dsi_send_to_fifo(dsi, xfer);
+
+ if (start)
+ exynos_dsi_transfer_start(dsi);
+}
+
+static int exynos_dsi_transfer(struct exynos_dsi *dsi,
+ struct exynos_dsi_transfer *xfers, unsigned int num_xfers)
+{
+ unsigned long flags;
+ bool stopped;
+ LIST_HEAD(xfer_list);
+
+ for (; num_xfers; --num_xfers, ++xfers) {
+ xfers->tx_done = 0;
+ xfers->rx_done = 0;
+ xfers->result = -ETIMEDOUT;
+ init_completion(&xfers->completed);
+
+ list_add_tail(&xfers->list, &xfer_list);
+ }
+
+ spin_lock_irqsave(&dsi->transfer_lock, flags);
+
+ stopped = list_empty(&dsi->transfer_list);
+ list_splice_tail(&xfer_list, &dsi->transfer_list);
+
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+
+ if (stopped)
+ exynos_dsi_transfer_start(dsi);
+
+ return 0;
+}
+
+/*
+ * Interrupt handler
+ */
+
+static irqreturn_t exynos_dsi_irq(int irq, void *dev_id)
+{
+ struct exynos_dsi *dsi = dev_id;
+ u32 status;
+
+ status = readl(dsi->reg_base + DSIM_INTSRC_REG);
+ writel(status, dsi->reg_base + DSIM_INTSRC_REG);
+
+ if (status & DSIM_INT_SW_RST_RELEASE) {
+ u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY);
+ writel(mask, dsi->reg_base + DSIM_INTMSK_REG);
+ return IRQ_HANDLED;
+ }
+
+ if (status & DSIM_INT_RX_DONE) {
+ exynos_dsi_transfer_finish(dsi, EXYNOS_DSI_RX);
+ return IRQ_HANDLED;
+ }
+
+ if (status & DSIM_INT_SFR_FIFO_EMPTY) {
+ exynos_dsi_transfer_finish(dsi, EXYNOS_DSI_TX);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+/*
+ * Display source
+ */
+
+static int exynos_dsi_set_stream(struct video_source *src,
+ enum video_source_stream_state state)
+{
+ struct exynos_dsi *dsi = src_to_dsi(src);
+
+ if (pm_runtime_suspended(dsi->dev))
+ return -EINVAL;
+
+ switch (state) {
+ case DISPLAY_ENTITY_STREAM_STOPPED:
+ exynos_dsi_set_display_enable(dsi, false);
+ dsi->streaming = false;
+ break;
+
+ case DISPLAY_ENTITY_STREAM_CONTINUOUS:
+ exynos_dsi_set_display_mode(dsi);
+ exynos_dsi_set_display_enable(dsi, true);
+ dsi->streaming = true;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* enable/disable dsi bus */
+static int exynos_dsi_enable(struct video_source *src)
+{
+ struct display_entity_interface_params params;
+ struct exynos_dsi *dsi = src_to_dsi(src);
+ int ret;
+
+ ret = display_entity_get_params(src->sink, ¶ms);
+ if (ret < 0)
+ return ret;
+ dsi->params = params.p.dsi;
+
+ ret = pm_runtime_get_sync(dsi->dev);
+ if (ret < 0)
+ return ret;
+
+ exynos_dsi_reset(dsi);
+ exynos_dsi_init_link(dsi);
+
+ return 0;
+}
+
+static int exynos_dsi_disable(struct video_source *src)
+{
+ struct exynos_dsi *dsi = src_to_dsi(src);
+
+ if (dsi->streaming)
+ return -EBUSY;
+
+ exynos_dsi_disable_clock(dsi);
+
+ return pm_runtime_put_sync(dsi->dev);
+}
+
+static void exynos_dsi_enable_hs(struct video_source *src, bool enable)
+{
+ struct exynos_dsi *dsi = src_to_dsi(src);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dsi->transfer_lock, flags);
+
+ dsi->hs_mode = enable;
+
+ spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+}
+
+/* data transfer */
+static int exynos_dsi_dcs_write(struct video_source *src, int channel,
+ const u8 *data, size_t len)
+{
+ struct exynos_dsi *dsi = src_to_dsi(src);
+ struct exynos_dsi_transfer xfer;
+ long ret;
+
+ if (!len)
+ return -EINVAL;
+
+ switch (len) {
+#if 0
+ /* FIXME: Fix handling of short packets */
+ case 1:
+ len = 0;
+ xfer.type = MIPI_DSI_DCS_SHORT_WRITE;
+ xfer.data[0] = data[0];
+ xfer.data[1] = 0;
+ break;
+ case 2:
+ len = 0;
+ xfer.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
+ xfer.data[0] = data[0];
+ xfer.data[1] = data[1];
+ break;
+#endif
+ default:
+ xfer.type = MIPI_DSI_DCS_LONG_WRITE;
+ xfer.data[0] = len & 0xff;
+ xfer.data[1] = len >> 8;
+ }
+
+ xfer.tx_payload = data;
+ xfer.tx_len = len;
+ xfer.rx_len = 0;
+
+ ret = exynos_dsi_transfer(dsi, &xfer, 1);
+ if (ret < 0)
+ return ret;
+
+ wait_for_completion_timeout(&xfer.completed,
+ msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
+ if (xfer.result = -ETIMEDOUT) {
+ exynos_dsi_transfer_cancel(dsi, &xfer);
+ dev_err(dsi->dev, "xfer timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ /* Also covers timeout condition */
+ return xfer.result;
+}
+
+static int exynos_dsi_dcs_read(struct video_source *src,
+ int channel, u8 dcs_cmd, u8 *data, size_t len)
+{
+ struct exynos_dsi *dsi = src_to_dsi(src);
+ struct exynos_dsi_transfer xfer;
+ long ret;
+
+ xfer.type = MIPI_DSI_DCS_READ;
+ xfer.tx_len = 0;
+ xfer.rx_payload = data;
+ xfer.rx_len = len;
+ xfer.data[0] = dcs_cmd;
+ xfer.data[1] = 0x00;
+
+ ret = exynos_dsi_transfer(dsi, &xfer, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = wait_for_completion_timeout(&xfer.completed,
+ msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
+ if (xfer.result = -ETIMEDOUT) {
+ exynos_dsi_transfer_cancel(dsi, &xfer);
+ dev_err(dsi->dev, "xfer timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ /* Also covers timeout condition */
+ return xfer.result;
+}
+
+static const struct common_video_source_ops exynos_dsi_common_ops = {
+ .set_stream = exynos_dsi_set_stream,
+};
+
+static const struct dsi_video_source_ops exynos_dsi_ops = {
+ .enable = exynos_dsi_enable,
+ .disable = exynos_dsi_disable,
+
+ .enable_hs = exynos_dsi_enable_hs,
+
+ .dcs_write = exynos_dsi_dcs_write,
+ .dcs_read = exynos_dsi_dcs_read,
+};
+
+#ifdef CONFIG_OF
+/*
+ * Device Tree
+ */
+
+static int (* const of_phy_enables[])(struct platform_device *, bool) = {
+#ifdef CONFIG_S5P_SETUP_MIPIPHY
+ [0] = s5p_dsim_phy_enable,
+#endif
+};
+
+static struct exynos_dsi_platform_data *exynos_dsi_parse_dt(
+ struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct exynos_dsi_platform_data *dsi_pd;
+ struct device *dev = &pdev->dev;
+ const __be32 *prop_data;
+ u32 val;
+
+ dsi_pd = kzalloc(sizeof(*dsi_pd), GFP_KERNEL);
+ if (!dsi_pd) {
+ dev_err(dev, "failed to allocate dsi platform data\n");
+ return NULL;
+ }
+
+ prop_data = of_get_property(node, "samsung,phy-type", NULL);
+ if (!prop_data) {
+ dev_err(dev, "failed to get phy-type property\n");
+ goto err_free_pd;
+ }
+
+ val = be32_to_cpu(*prop_data);
+ if (val >= ARRAY_SIZE(of_phy_enables) || !of_phy_enables[val]) {
+ dev_err(dev, "Invalid phy-type %u\n", val);
+ goto err_free_pd;
+ }
+ dsi_pd->phy_enable = of_phy_enables[val];
+
+ prop_data = of_get_property(node, "samsung,pll-stable-time", NULL);
+ if (!prop_data) {
+ dev_err(dev, "failed to get pll-stable-time property\n");
+ goto err_free_pd;
+ }
+ dsi_pd->pll_stable_time = be32_to_cpu(*prop_data);
+
+ prop_data = of_get_property(node, "samsung,stop-holding-count", NULL);
+ if (!prop_data) {
+ dev_err(dev, "failed to get stop-holding-count property\n");
+ goto err_free_pd;
+ }
+ dsi_pd->stop_holding_cnt = be32_to_cpu(*prop_data);
+
+ prop_data = of_get_property(node, "samsung,bta-timeout", NULL);
+ if (!prop_data) {
+ dev_err(dev, "failed to get bta-timeout property\n");
+ goto err_free_pd;
+ }
+ dsi_pd->bta_timeout = be32_to_cpu(*prop_data);
+
+ prop_data = of_get_property(node, "samsung,rx-timeout", NULL);
+ if (!prop_data) {
+ dev_err(dev, "failed to get rx-timeout property\n");
+ goto err_free_pd;
+ }
+ dsi_pd->rx_timeout = be32_to_cpu(*prop_data);
+
+ prop_data = of_get_property(node, "samsung,pll-clk-freq", NULL);
+ if (!prop_data) {
+ dev_err(dev, "failed to get pll-clk-freq property\n");
+ goto err_free_pd;
+ }
+ dsi_pd->pll_clk_rate = be32_to_cpu(*prop_data);
+
+ return dsi_pd;
+
+err_free_pd:
+ kfree(dsi_pd);
+
+ return NULL;
+}
+
+static struct of_device_id exynos_dsi_of_match[] = {
+ { .compatible = "samsung,exynos4210-mipi-dsi" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, exynos_dsi_of_match);
+#else
+static struct exynos_dsi_platform_data *exynos_dsi_parse_dt(
+ struct platform_device *pdev)
+{
+ return NULL;
+}
+#endif
+
+/*
+ * Platform driver
+ */
+
+static int exynos_dsi_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct exynos_dsi *dsi;
+ int ret;
+
+ dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi) {
+ dev_err(&pdev->dev, "failed to allocate dsi object.\n");
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&dsi->transfer_lock);
+ INIT_LIST_HEAD(&dsi->transfer_list);
+
+ dsi->dev = &pdev->dev;
+ dsi->pd = pdev->dev.platform_data;
+
+ if (dsi->pd = NULL && pdev->dev.of_node)
+ dsi->pd = exynos_dsi_parse_dt(pdev);
+
+ if (dsi->pd = NULL) {
+ dev_err(&pdev->dev, "failed to get platform data for dsi.\n");
+ return -EINVAL;
+ }
+
+ dsi->supplies[0].supply = "vdd11";
+ dsi->supplies[1].supply = "vdd18";
+ ret = devm_regulator_bulk_get(&pdev->dev,
+ ARRAY_SIZE(dsi->supplies), dsi->supplies);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret);
+ return ret;
+ }
+
+ dsi->pll_clk = devm_clk_get(&pdev->dev, "pll_clk");
+ if (IS_ERR(dsi->pll_clk)) {
+ dev_err(&pdev->dev, "failed to get dsi pll input clock\n");
+ return -ENODEV;
+ }
+
+ dsi->bus_clk = devm_clk_get(&pdev->dev, "bus_clk");
+ if (IS_ERR(dsi->bus_clk)) {
+ dev_err(&pdev->dev, "failed to get dsi bus clock\n");
+ return -ENODEV;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to get io memory region\n");
+ return -ENODEV;
+ }
+
+ dsi->reg_base = devm_request_and_ioremap(&pdev->dev, res);
+ if (!dsi->reg_base) {
+ dev_err(&pdev->dev, "failed to remap io region\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, dsi);
+
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ pm_runtime_get_sync(&pdev->dev);
+ writel(~0, dsi->reg_base + DSIM_INTMSK_REG);
+ pm_runtime_put_sync(&pdev->dev);
+
+ dsi->irq = platform_get_irq(pdev, 0);
+ if (dsi->irq < 0) {
+ dev_err(&pdev->dev, "failed to request dsi irq resource\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(&pdev->dev, dsi->irq, exynos_dsi_irq,
+ 0, dev_name(&pdev->dev), dsi);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request dsi irq\n");
+ return ret;
+ }
+
+ dsi->out.name = dev_name(&pdev->dev);
+ dsi->out.id = -1;
+ dsi->out.of_node = pdev->dev.of_node;
+ dsi->out.common_ops = &exynos_dsi_common_ops;
+ dsi->out.ops.dsi = &exynos_dsi_ops;
+
+ ret = video_source_register(&dsi->out);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register video source\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit exynos_dsi_remove(struct platform_device *pdev)
+{
+ struct exynos_dsi *dsi = platform_get_drvdata(pdev);
+
+ video_source_unregister(&dsi->out);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+/*
+ * Power management
+ */
+
+static int exynos_dsi_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_dsi *dsi = platform_get_drvdata(pdev);
+
+ dev_info(dev, "%s\n", __func__);
+
+ if (dsi->pd->phy_enable)
+ dsi->pd->phy_enable(pdev, false);
+
+ clk_disable_unprepare(dsi->pll_clk);
+ clk_disable_unprepare(dsi->bus_clk);
+
+ regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
+
+ return 0;
+}
+
+static int exynos_dsi_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_dsi *dsi = platform_get_drvdata(pdev);
+
+ dev_info(dev, "%s\n", __func__);
+
+ regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
+
+ clk_prepare_enable(dsi->bus_clk);
+ clk_prepare_enable(dsi->pll_clk);
+
+ if (dsi->pd->phy_enable)
+ dsi->pd->phy_enable(pdev, true);
+
+ return 0;
+}
+
+static int exynos_dsi_suspend(struct device *dev)
+{
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ return exynos_dsi_runtime_suspend(dev);
+}
+
+static int exynos_dsi_resume(struct device *dev)
+{
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ return exynos_dsi_runtime_resume(dev);
+}
+
+static const struct dev_pm_ops exynos_dsi_pm_ops = {
+ SET_RUNTIME_PM_OPS(exynos_dsi_runtime_suspend,
+ exynos_dsi_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume)
+};
+
+/*
+ * Module
+ */
+
+static struct platform_driver exynos_dsi_driver = {
+ .probe = exynos_dsi_probe,
+ .remove = __devexit_p(exynos_dsi_remove),
+ .driver = {
+ .name = "exynos-dsi",
+ .owner = THIS_MODULE,
+ .pm = &exynos_dsi_pm_ops,
+ .of_match_table = of_match_ptr(exynos_dsi_of_match),
+ },
+};
+
+module_platform_driver(exynos_dsi_driver);
+
+MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>");
+MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master");
+MODULE_LICENSE("GPL");
diff --git a/include/video/exynos_dsi.h b/include/video/exynos_dsi.h
new file mode 100644
index 0000000..95e1568
--- /dev/null
+++ b/include/video/exynos_dsi.h
@@ -0,0 +1,41 @@
+/* include/video/exynos_mipi_dsim.h
+ *
+ * Platform data header for Samsung SoC MIPI-DSIM.
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd
+ *
+ * InKi Dae <inki.dae@samsung.com>
+ * Donghwa Lee <dh09.lee@samsung.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.
+*/
+
+#ifndef _EXYNOS_MIPI_DSIM_H
+#define _EXYNOS_MIPI_DSIM_H
+
+#include <linux/device.h>
+
+/*
+ * struct exynos_dsi_platform_data - interface to platform data
+ * for mipi-dsi driver.
+ *
+ * TODO
+ */
+struct exynos_dsi_platform_data {
+ unsigned int enabled;
+
+ int (*phy_enable)(struct platform_device *pdev, bool on);
+
+ unsigned int pll_stable_time;
+ unsigned long pll_clk_rate;
+ unsigned long esc_clk_rate;
+ unsigned short stop_holding_cnt;
+ unsigned char bta_timeout;
+ unsigned short rx_timeout;
+};
+
+int s5p_dsim_phy_enable(struct platform_device *pdev, bool on);
+
+#endif /* _EXYNOS_MIPI_DSIM_H */
--
1.8.1
^ permalink raw reply related
* [RFC PATCH 2/4] video: add makefile & kconfig
From: Tomasz Figa @ 2013-01-30 15:39 UTC (permalink / raw)
To: dri-devel
Cc: linux-fbdev, linux-samsung-soc, kyungmin.park, m.szyprowski,
t.figa, tomasz.figa, Daniel Vetter, Marcus Lorentzon,
Laurent Pinchart, rob, tomi.valkeinen, Vikas Sajjan, inki.dae,
dh09.lee, ville.syrjala, s.nawrocki
In-Reply-To: <1359560343-31636-1-git-send-email-t.figa@samsung.com>
Signed-off-by: Tomasz Figa <t.figa@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/video/Kconfig | 1 +
drivers/video/Makefile | 1 +
drivers/video/display/Kconfig | 10 ++++++++++
drivers/video/display/Makefile | 1 +
4 files changed, 13 insertions(+)
create mode 100644 drivers/video/display/Kconfig
create mode 100644 drivers/video/display/Makefile
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 68f17ee..800e98c 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2444,6 +2444,7 @@ source "drivers/video/omap/Kconfig"
source "drivers/video/omap2/Kconfig"
source "drivers/video/exynos/Kconfig"
source "drivers/video/backlight/Kconfig"
+source "drivers/video/display/Kconfig"
if VT
source "drivers/video/console/Kconfig"
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index f592f3b..4a091d4 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -14,6 +14,7 @@ fb-objs := $(fb-y)
obj-$(CONFIG_VT) += console/
obj-$(CONFIG_LOGO) += logo/
obj-y += backlight/
+obj-y += display/
obj-$(CONFIG_EXYNOS_VIDEO) += exynos/
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig
new file mode 100644
index 0000000..477192d
--- /dev/null
+++ b/drivers/video/display/Kconfig
@@ -0,0 +1,10 @@
+menuconfig DISPLAY_CORE
+ tristate "Display Core"
+ ---help---
+ Support common display framework for graphics devices.
+
+if DISPLAY_CORE
+
+
+
+endif # DISPLAY_CORE
diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile
new file mode 100644
index 0000000..bd93496
--- /dev/null
+++ b/drivers/video/display/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DISPLAY_CORE) += display-core.o
--
1.8.1
^ permalink raw reply related
* [RFC PATCH 1/4] video: add display-core
From: Tomasz Figa @ 2013-01-30 15:39 UTC (permalink / raw)
To: dri-devel
Cc: linux-fbdev, linux-samsung-soc, kyungmin.park, m.szyprowski,
t.figa, tomasz.figa, Daniel Vetter, Marcus Lorentzon,
Laurent Pinchart, rob, tomi.valkeinen, Vikas Sajjan, inki.dae,
dh09.lee, ville.syrjala, s.nawrocki
In-Reply-To: <1359560343-31636-1-git-send-email-t.figa@samsung.com>
Signed-off-by: Tomasz Figa <t.figa@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/video/display/display-core.c | 295 +++++++++++++++++++++++++++++++++++
include/video/display.h | 230 +++++++++++++++++++++++++++
2 files changed, 525 insertions(+)
create mode 100644 drivers/video/display/display-core.c
create mode 100644 include/video/display.h
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c
new file mode 100644
index 0000000..ed49384
--- /dev/null
+++ b/drivers/video/display/display-core.c
@@ -0,0 +1,295 @@
+/*
+ * Display Core
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.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.
+ */
+
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/videomode.h>
+
+#include <video/display.h>
+
+static struct video_source *video_source_bind(struct display_entity *entity);
+static void video_source_unbind(struct display_entity *entity);
+
+/* -----------------------------------------------------------------------------
+ * Display Entity
+ */
+
+static LIST_HEAD(display_entity_list);
+static DEFINE_MUTEX(display_entity_mutex);
+
+struct display_entity *display_entity_get_first(void)
+{
+ /* FIXME: Don't we need some locking here? */
+
+ if (list_empty(&display_entity_list))
+ return NULL;
+
+ return list_first_entry(&display_entity_list, struct display_entity,
+ list);
+}
+EXPORT_SYMBOL(display_entity_get_first);
+
+int display_entity_set_state(struct display_entity *entity,
+ enum display_entity_state state)
+{
+ int ret;
+
+ if (entity->state = state)
+ return 0;
+
+ if (!entity->ops || !entity->ops->set_state)
+ return 0;
+
+ ret = entity->ops->set_state(entity, state);
+ if (ret < 0)
+ return ret;
+
+ entity->state = state;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(display_entity_set_state);
+
+int display_entity_get_modes(struct display_entity *entity,
+ const struct videomode **modes)
+{
+ if (!entity->ops || !entity->ops->get_modes)
+ return 0;
+
+ return entity->ops->get_modes(entity, modes);
+}
+EXPORT_SYMBOL_GPL(display_entity_get_modes);
+
+int display_entity_get_size(struct display_entity *entity,
+ unsigned int *width, unsigned int *height)
+{
+ if (!entity->ops || !entity->ops->get_size)
+ return -EOPNOTSUPP;
+
+ return entity->ops->get_size(entity, width, height);
+}
+EXPORT_SYMBOL_GPL(display_entity_get_size);
+
+int display_entity_get_params(struct display_entity *entity,
+ struct display_entity_interface_params *params)
+{
+ if (!entity->ops || !entity->ops->get_params)
+ return -EOPNOTSUPP;
+
+ return entity->ops->get_params(entity, params);
+}
+EXPORT_SYMBOL_GPL(display_entity_get_params);
+
+static void display_entity_release(struct kref *ref)
+{
+ struct display_entity *entity + container_of(ref, struct display_entity, ref);
+
+ if (entity->release)
+ entity->release(entity);
+}
+
+struct display_entity *display_entity_get(struct display_entity *entity)
+{
+ if (entity = NULL)
+ return NULL;
+
+ kref_get(&entity->ref);
+ return entity;
+}
+EXPORT_SYMBOL_GPL(display_entity_get);
+
+void display_entity_put(struct display_entity *entity)
+{
+ kref_put(&entity->ref, display_entity_release);
+}
+EXPORT_SYMBOL_GPL(display_entity_put);
+
+int __must_check __display_entity_register(struct display_entity *entity,
+ struct module *owner)
+{
+ struct video_source *src;
+
+ kref_init(&entity->ref);
+ entity->owner = owner;
+ entity->state = DISPLAY_ENTITY_STATE_OFF;
+ entity->source = NULL;
+
+ src = video_source_bind(entity);
+ if (!src)
+ return -EPROBE_DEFER;
+
+ mutex_lock(&display_entity_mutex);
+ list_add(&entity->list, &display_entity_list);
+ mutex_unlock(&display_entity_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__display_entity_register);
+
+void display_entity_unregister(struct display_entity *entity)
+{
+ video_source_unbind(entity);
+
+ mutex_lock(&display_entity_mutex);
+
+ list_del(&entity->list);
+ mutex_unlock(&display_entity_mutex);
+
+ display_entity_put(entity);
+}
+EXPORT_SYMBOL_GPL(display_entity_unregister);
+
+/* -----------------------------------------------------------------------------
+ * Video Source
+ */
+
+static LIST_HEAD(video_source_list);
+static DEFINE_MUTEX(video_source_mutex);
+
+static void video_source_release(struct kref *ref)
+{
+ struct video_source *src + container_of(ref, struct video_source, ref);
+
+ if (src->release)
+ src->release(src);
+}
+
+static struct video_source *video_source_get(struct video_source *src)
+{
+ if (src = NULL)
+ return NULL;
+
+ kref_get(&src->ref);
+ if (!try_module_get(src->owner)) {
+ kref_put(&src->ref, video_source_release);
+ return NULL;
+ }
+
+ return src;
+}
+
+static void video_source_put(struct video_source *src)
+{
+ module_put(src->owner);
+ kref_put(&src->ref, video_source_release);
+}
+
+int __must_check __video_source_register(struct video_source *src,
+ struct module *owner)
+{
+ kref_init(&src->ref);
+ src->owner = owner;
+
+ mutex_lock(&video_source_mutex);
+ list_add(&src->list, &video_source_list);
+
+ mutex_unlock(&video_source_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__video_source_register);
+
+void video_source_unregister(struct video_source *src)
+{
+ mutex_lock(&video_source_mutex);
+
+ list_del(&src->list);
+ mutex_unlock(&video_source_mutex);
+
+ kref_put(&src->ref, video_source_release);
+}
+EXPORT_SYMBOL_GPL(video_source_unregister);
+
+static struct video_source *video_source_bind(struct display_entity *entity)
+{
+ struct video_source *src = NULL;
+ int ret;
+
+ if (entity->source)
+ return entity->source;
+
+ mutex_lock(&video_source_mutex);
+
+ if (entity->of_node) {
+ struct device_node *np;
+
+ np = of_parse_phandle(entity->of_node, "video-source", 0);
+ if (!np)
+ goto unlock;
+
+ list_for_each_entry(src, &video_source_list, list) {
+ if (src->of_node = np)
+ goto found;
+ }
+
+ src = NULL;
+ goto unlock;
+ }
+
+ if (!entity->src_name)
+ goto unlock;
+
+ list_for_each_entry(src, &video_source_list, list) {
+ if (src->id != entity->src_id)
+ continue;
+ if (!strcmp(src->name, entity->src_name))
+ goto found;
+ }
+
+ src = NULL;
+ goto unlock;
+
+found:
+ video_source_get(src);
+
+ if (src->common_ops->bind) {
+ ret = src->common_ops->bind(src, entity);
+ if (ret != 0) {
+ video_source_put(src);
+ src = NULL;
+ goto unlock;
+ }
+ }
+
+ src->sink = entity;
+ entity->source = src;
+
+unlock:
+ mutex_unlock(&video_source_mutex);
+
+ return src;
+}
+
+static void video_source_unbind(struct display_entity *entity)
+{
+ struct video_source *src = entity->source;
+
+ if (!src)
+ return;
+
+ if (src->common_ops && src->common_ops->unbind)
+ src->common_ops->unbind(src, entity);
+
+ src->sink = NULL;
+ entity->source = NULL;
+
+ video_source_put(src);
+}
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Display Core");
+MODULE_LICENSE("GPL");
diff --git a/include/video/display.h b/include/video/display.h
new file mode 100644
index 0000000..7ffea2c
--- /dev/null
+++ b/include/video/display.h
@@ -0,0 +1,230 @@
+/*
+ * Display Core
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ *
+ * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.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.
+ */
+
+#ifndef __DISPLAY_H__
+#define __DISPLAY_H__
+
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <video/omapdss.h>
+
+struct display_entity;
+struct video_source;
+struct videomode;
+
+/* -----------------------------------------------------------------------------
+ * Display Entity
+ */
+
+/* Hack to get the first registered display entity */
+struct display_entity *display_entity_get_first(void);
+
+enum display_entity_state {
+ DISPLAY_ENTITY_STATE_OFF,
+ DISPLAY_ENTITY_STATE_STANDBY,
+ DISPLAY_ENTITY_STATE_ON,
+};
+
+enum display_entity_interface_type {
+ DISPLAY_ENTITY_INTERFACE_DPI,
+ DISPLAY_ENTITY_INTERFACE_DSI,
+};
+
+#define DSI_MODE_VIDEO (1 << 0)
+#define DSI_MODE_VIDEO_BURST (1 << 1)
+#define DSI_MODE_VIDEO_SYNC_PULSE (1 << 2)
+#define DSI_MODE_VIDEO_AUTO_VERT (1 << 3)
+#define DSI_MODE_VIDEO_HSE (1 << 4)
+#define DSI_MODE_VIDEO_HFP (1 << 5)
+#define DSI_MODE_VIDEO_HBP (1 << 6)
+#define DSI_MODE_VIDEO_HSA (1 << 7)
+#define DSI_MODE_VSYNC_FLUSH (1 << 8)
+#define DSI_MODE_EOT_PACKET (1 << 9)
+
+enum mipi_dsi_pixel_format {
+ DSI_FMT_RGB888,
+ DSI_FMT_RGB666,
+ DSI_FMT_RGB666_PACKED,
+ DSI_FMT_RGB565,
+};
+
+struct mipi_dsi_interface_params {
+ enum mipi_dsi_pixel_format format;
+ unsigned long mode;
+ unsigned long hs_clk_freq;
+ unsigned long esc_clk_freq;
+ unsigned char data_lanes;
+ unsigned char cmd_allow;
+};
+
+struct display_entity_interface_params {
+ enum display_entity_interface_type type;
+ union {
+ struct mipi_dsi_interface_params dsi;
+ } p;
+};
+
+struct display_entity_control_ops {
+ int (*set_state)(struct display_entity *ent,
+ enum display_entity_state state);
+ int (*update)(struct display_entity *ent,
+ void (*callback)(int, void *), void *data);
+ int (*get_modes)(struct display_entity *ent,
+ const struct videomode **modes);
+ int (*get_params)(struct display_entity *ent,
+ struct display_entity_interface_params *params);
+ int (*get_size)(struct display_entity *ent,
+ unsigned int *width, unsigned int *height);
+};
+
+struct display_entity {
+ struct list_head list;
+ struct device *dev;
+ struct device_node *of_node;
+ struct module *owner;
+ struct kref ref;
+
+ const char *src_name;
+ int src_id;
+ struct video_source *source;
+
+ const struct display_entity_control_ops *ops;
+
+ void(*release)(struct display_entity *ent);
+
+ enum display_entity_state state;
+};
+
+int display_entity_set_state(struct display_entity *entity,
+ enum display_entity_state state);
+int display_entity_get_params(struct display_entity *entity,
+ struct display_entity_interface_params *params);
+int display_entity_get_modes(struct display_entity *entity,
+ const struct videomode **modes);
+int display_entity_get_size(struct display_entity *entity,
+ unsigned int *width, unsigned int *height);
+
+struct display_entity *display_entity_get(struct display_entity *entity);
+void display_entity_put(struct display_entity *entity);
+
+int __must_check __display_entity_register(struct display_entity *entity,
+ struct module *owner);
+void display_entity_unregister(struct display_entity *entity);
+
+#define display_entity_register(display_entity) \
+ __display_entity_register(display_entity, THIS_MODULE)
+
+
+/* -----------------------------------------------------------------------------
+ * Video Source
+ */
+
+enum video_source_stream_state {
+ DISPLAY_ENTITY_STREAM_STOPPED,
+ DISPLAY_ENTITY_STREAM_CONTINUOUS,
+};
+
+struct common_video_source_ops {
+ int (*set_stream)(struct video_source *src,
+ enum video_source_stream_state state);
+ int (*bind)(struct video_source *src, struct display_entity *sink);
+ int (*unbind)(struct video_source *src, struct display_entity *sink);
+};
+
+struct dpi_video_source_ops {
+ int (*set_videomode)(struct video_source *src,
+ const struct videomode *vm);
+ int (*set_data_lines)(struct video_source *src, int lines);
+};
+
+struct dsi_video_source_ops {
+ /* enable/disable dsi bus */
+ int (*enable)(struct video_source *src);
+ int (*disable)(struct video_source *src);
+
+ /* bus configuration */
+ int (*configure_pins)(struct video_source *src,
+ const struct omap_dsi_pin_config *pins);
+ int (*set_clocks)(struct video_source *src,
+ unsigned long ddr_clk,
+ unsigned long lp_clk);
+ /* NOTE: Do we really need configure_pins and set_clocks here? */
+
+ void (*enable_hs)(struct video_source *src, bool enable);
+
+ /* data transfer */
+ int (*dcs_write)(struct video_source *src, int channel,
+ const u8 *data, size_t len);
+ int (*dcs_read)(struct video_source *src, int channel, u8 dcs_cmd,
+ u8 *data, size_t len);
+ /* NOTE: Do we need more write and read types? */
+
+ int (*update)(struct video_source *src, int channel,
+ void (*callback)(int, void *), void *data);
+};
+
+struct dvi_video_source_ops {
+ int (*set_videomode)(struct video_source *src,
+ const struct videomode *vm);
+};
+
+struct video_source {
+ struct list_head list;
+ struct device *dev;
+ struct device_node *of_node;
+ struct module *owner;
+ struct kref ref;
+
+ struct display_entity *sink;
+
+ const char *name;
+ int id;
+
+ const struct common_video_source_ops *common_ops;
+
+ union {
+ const struct dpi_video_source_ops *dpi;
+ const struct dsi_video_source_ops *dsi;
+ const struct dvi_video_source_ops *dvi;
+ } ops;
+
+ void(*release)(struct video_source *src);
+};
+
+static inline int dsi_dcs_write(struct video_source *src, int channel,
+ const u8 *data, size_t len)
+{
+ if (!src->ops.dsi || !src->ops.dsi->dcs_write)
+ return -EINVAL;
+
+ return src->ops.dsi->dcs_write(src, channel, data, len);
+}
+
+static inline int dsi_dcs_read(struct video_source *src, int channel,
+ u8 dcs_cmd, u8 *data, size_t len)
+{
+ if (!src->ops.dsi || !src->ops.dsi->dcs_read)
+ return -EINVAL;
+
+ return src->ops.dsi->dcs_read(src, channel, dcs_cmd, data, len);
+}
+
+
+#define video_source_register(video_source) \
+ __video_source_register(video_source, THIS_MODULE)
+
+int __must_check __video_source_register(struct video_source *entity,
+ struct module *owner);
+void video_source_unregister(struct video_source *entity);
+
+#endif /* __DISPLAY_H__ */
--
1.8.1
^ 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