* Re: [PATCH net-next] ibmveth: v1 calculate correct gso_size and set gso_type
From: Brian King @ 2016-11-09 16:02 UTC (permalink / raw)
To: Jonathan Maxwell
Cc: Eric Dumazet, Thomas Falcon, Jon Maxwell, hofrat, linux-kernel,
jarod, netdev, paulus, Tom Herbert, Marcelo Ricardo Leitner,
linuxppc-dev, David Miller
In-Reply-To: <CAGHK07Cvh2QnGrUg5eu_aC=cV7DaL9wJm8adZ0rv4s0DmF-tzw@mail.gmail.com>
On 11/06/2016 03:22 PM, Jonathan Maxwell wrote:
> On Thu, Nov 3, 2016 at 8:40 AM, Brian King <brking@linux.vnet.ibm.com> wrote:
>> On 10/27/2016 10:26 AM, Eric Dumazet wrote:
>>> On Wed, 2016-10-26 at 11:09 +1100, Jon Maxwell wrote:
>>>> We recently encountered a bug where a few customers using ibmveth on the
>>>> same LPAR hit an issue where a TCP session hung when large receive was
>>>> enabled. Closer analysis revealed that the session was stuck because the
>>>> one side was advertising a zero window repeatedly.
>>>>
>>>> We narrowed this down to the fact the ibmveth driver did not set gso_size
>>>> which is translated by TCP into the MSS later up the stack. The MSS is
>>>> used to calculate the TCP window size and as that was abnormally large,
>>>> it was calculating a zero window, even although the sockets receive buffer
>>>> was completely empty.
>>>>
>>>> We were able to reproduce this and worked with IBM to fix this. Thanks Tom
>>>> and Marcelo for all your help and review on this.
>>>>
>>>> The patch fixes both our internal reproduction tests and our customers tests.
>>>>
>>>> Signed-off-by: Jon Maxwell <jmaxwell37@gmail.com>
>>>> ---
>>>> drivers/net/ethernet/ibm/ibmveth.c | 20 ++++++++++++++++++++
>>>> 1 file changed, 20 insertions(+)
>>>>
>>>> diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c
>>>> index 29c05d0..c51717e 100644
>>>> --- a/drivers/net/ethernet/ibm/ibmveth.c
>>>> +++ b/drivers/net/ethernet/ibm/ibmveth.c
>>>> @@ -1182,6 +1182,8 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
>>>> int frames_processed = 0;
>>>> unsigned long lpar_rc;
>>>> struct iphdr *iph;
>>>> + bool large_packet = 0;
>>>> + u16 hdr_len = ETH_HLEN + sizeof(struct tcphdr);
>>>>
>>>> restart_poll:
>>>> while (frames_processed < budget) {
>>>> @@ -1236,10 +1238,28 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
>>>> iph->check = 0;
>>>> iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
>>>> adapter->rx_large_packets++;
>>>> + large_packet = 1;
>>>> }
>>>> }
>>>> }
>>>>
>>>> + if (skb->len > netdev->mtu) {
>>>> + iph = (struct iphdr *)skb->data;
>>>> + if (be16_to_cpu(skb->protocol) == ETH_P_IP &&
>>>> + iph->protocol == IPPROTO_TCP) {
>>>> + hdr_len += sizeof(struct iphdr);
>>>> + skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
>>>> + skb_shinfo(skb)->gso_size = netdev->mtu - hdr_len;
>>>> + } else if (be16_to_cpu(skb->protocol) == ETH_P_IPV6 &&
>>>> + iph->protocol == IPPROTO_TCP) {
>>>> + hdr_len += sizeof(struct ipv6hdr);
>>>> + skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
>>>> + skb_shinfo(skb)->gso_size = netdev->mtu - hdr_len;
>>>> + }
>>>> + if (!large_packet)
>>>> + adapter->rx_large_packets++;
>>>> + }
>>>> +
>>>>
>>>
>>> This might break forwarding and PMTU discovery.
>>>
>>> You force gso_size to device mtu, regardless of real MSS used by the TCP
>>> sender.
>>>
>>> Don't you have the MSS provided in RX descriptor, instead of guessing
>>> the value ?
>>
>> We've had some further discussions on this with the Virtual I/O Server (VIOS)
>> development team. The large receive aggregation in the VIOS (AIX based) is actually
>> being done by software in the VIOS. What they may be able to do is when performing
>> this aggregation, they could look at the packet lengths of all the packets being
>> aggregated and take the largest packet size within the aggregation unit, minus the
>> header length and return that to the virtual ethernet client which we could then stuff
>> into gso_size. They are currently assessing how feasible this would be to do and whether
>> it would impact other bits of the code. However, assuming this does end up being an option,
>> would this address the concerns here or is that going to break something else I'm
>> not thinking of?
>
> I was discussing this with a colleague and although this is better than
> what we have so far. We wonder if there could be a corner case where
> it ends up with a smaller value than the current MSS. For example if
> the application sent a burst of small TCP packets with the PUSH
> bit set. In that case they may not be coalesced by GRO. The VIOS could
Would that be a big problem though? Other than a performance degradation
in this specific case, do you see a functional issue with this approach?
> probably be coded to detect that condition and use the previous MSS.
> But that may not necessarily be the current MSS.
>
> The ibmveth driver passes the MSS via gso_size to the VIOS. Either as the
> 3rd argument of ibmveth_send() or via tcp_hdr(skb)->check which is
> presumably over-written when the VIOS does the TSO. Would it be possible
> to keep a copy of this value on the TX side on the VIOS before it over-written
> and then some how pass that up to the RX side along with frame and set
> gso_size to that which should be the current MSS?
That seems like it might be more difficult. Wouldn't that require the VIOS
to track the MSS on a per connection basis, since the MSS might differ
based on the source / destination? I discussed with VIOS development, and
they don't do any connection tracking where they'd be able to insert this.
Unless there is a functional issue with the approach to have the VIOS insert
the MSS based on the size of the packets received off the wire, I think that
might be our best option...
Thanks,
Brian
>
> Regards
>
> Jon
>
>>
>> Unfortunately, I don't think we'd have a good way to get gso_segs set correctly as I don't
>> see how that would get passed back up the interface.
>>
>> Thanks,
>>
>> Brian
>>
>>
>> --
>> Brian King
>> Power Linux I/O
>> IBM Linux Technology Center
>>
>
--
Brian King
Power Linux I/O
IBM Linux Technology Center
^ permalink raw reply
* Re: [PATCH net-next] ibmveth: v1 calculate correct gso_size and set gso_type
From: Brian King @ 2016-11-09 16:02 UTC (permalink / raw)
To: Jonathan Maxwell
Cc: Thomas Falcon, Eric Dumazet, netdev, Jon Maxwell, linux-kernel,
jarod, paulus, hofrat, Marcelo Ricardo Leitner, Tom Herbert,
linuxppc-dev, David Miller
In-Reply-To: <CAGHK07Cvh2QnGrUg5eu_aC=cV7DaL9wJm8adZ0rv4s0DmF-tzw@mail.gmail.com>
On 11/06/2016 03:22 PM, Jonathan Maxwell wrote:
> On Thu, Nov 3, 2016 at 8:40 AM, Brian King <brking@linux.vnet.ibm.com> wrote:
>> On 10/27/2016 10:26 AM, Eric Dumazet wrote:
>>> On Wed, 2016-10-26 at 11:09 +1100, Jon Maxwell wrote:
>>>> We recently encountered a bug where a few customers using ibmveth on the
>>>> same LPAR hit an issue where a TCP session hung when large receive was
>>>> enabled. Closer analysis revealed that the session was stuck because the
>>>> one side was advertising a zero window repeatedly.
>>>>
>>>> We narrowed this down to the fact the ibmveth driver did not set gso_size
>>>> which is translated by TCP into the MSS later up the stack. The MSS is
>>>> used to calculate the TCP window size and as that was abnormally large,
>>>> it was calculating a zero window, even although the sockets receive buffer
>>>> was completely empty.
>>>>
>>>> We were able to reproduce this and worked with IBM to fix this. Thanks Tom
>>>> and Marcelo for all your help and review on this.
>>>>
>>>> The patch fixes both our internal reproduction tests and our customers tests.
>>>>
>>>> Signed-off-by: Jon Maxwell <jmaxwell37@gmail.com>
>>>> ---
>>>> drivers/net/ethernet/ibm/ibmveth.c | 20 ++++++++++++++++++++
>>>> 1 file changed, 20 insertions(+)
>>>>
>>>> diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c
>>>> index 29c05d0..c51717e 100644
>>>> --- a/drivers/net/ethernet/ibm/ibmveth.c
>>>> +++ b/drivers/net/ethernet/ibm/ibmveth.c
>>>> @@ -1182,6 +1182,8 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
>>>> int frames_processed = 0;
>>>> unsigned long lpar_rc;
>>>> struct iphdr *iph;
>>>> + bool large_packet = 0;
>>>> + u16 hdr_len = ETH_HLEN + sizeof(struct tcphdr);
>>>>
>>>> restart_poll:
>>>> while (frames_processed < budget) {
>>>> @@ -1236,10 +1238,28 @@ static int ibmveth_poll(struct napi_struct *napi, int budget)
>>>> iph->check = 0;
>>>> iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
>>>> adapter->rx_large_packets++;
>>>> + large_packet = 1;
>>>> }
>>>> }
>>>> }
>>>>
>>>> + if (skb->len > netdev->mtu) {
>>>> + iph = (struct iphdr *)skb->data;
>>>> + if (be16_to_cpu(skb->protocol) == ETH_P_IP &&
>>>> + iph->protocol == IPPROTO_TCP) {
>>>> + hdr_len += sizeof(struct iphdr);
>>>> + skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
>>>> + skb_shinfo(skb)->gso_size = netdev->mtu - hdr_len;
>>>> + } else if (be16_to_cpu(skb->protocol) == ETH_P_IPV6 &&
>>>> + iph->protocol == IPPROTO_TCP) {
>>>> + hdr_len += sizeof(struct ipv6hdr);
>>>> + skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
>>>> + skb_shinfo(skb)->gso_size = netdev->mtu - hdr_len;
>>>> + }
>>>> + if (!large_packet)
>>>> + adapter->rx_large_packets++;
>>>> + }
>>>> +
>>>>
>>>
>>> This might break forwarding and PMTU discovery.
>>>
>>> You force gso_size to device mtu, regardless of real MSS used by the TCP
>>> sender.
>>>
>>> Don't you have the MSS provided in RX descriptor, instead of guessing
>>> the value ?
>>
>> We've had some further discussions on this with the Virtual I/O Server (VIOS)
>> development team. The large receive aggregation in the VIOS (AIX based) is actually
>> being done by software in the VIOS. What they may be able to do is when performing
>> this aggregation, they could look at the packet lengths of all the packets being
>> aggregated and take the largest packet size within the aggregation unit, minus the
>> header length and return that to the virtual ethernet client which we could then stuff
>> into gso_size. They are currently assessing how feasible this would be to do and whether
>> it would impact other bits of the code. However, assuming this does end up being an option,
>> would this address the concerns here or is that going to break something else I'm
>> not thinking of?
>
> I was discussing this with a colleague and although this is better than
> what we have so far. We wonder if there could be a corner case where
> it ends up with a smaller value than the current MSS. For example if
> the application sent a burst of small TCP packets with the PUSH
> bit set. In that case they may not be coalesced by GRO. The VIOS could
Would that be a big problem though? Other than a performance degradation
in this specific case, do you see a functional issue with this approach?
> probably be coded to detect that condition and use the previous MSS.
> But that may not necessarily be the current MSS.
>
> The ibmveth driver passes the MSS via gso_size to the VIOS. Either as the
> 3rd argument of ibmveth_send() or via tcp_hdr(skb)->check which is
> presumably over-written when the VIOS does the TSO. Would it be possible
> to keep a copy of this value on the TX side on the VIOS before it over-written
> and then some how pass that up to the RX side along with frame and set
> gso_size to that which should be the current MSS?
That seems like it might be more difficult. Wouldn't that require the VIOS
to track the MSS on a per connection basis, since the MSS might differ
based on the source / destination? I discussed with VIOS development, and
they don't do any connection tracking where they'd be able to insert this.
Unless there is a functional issue with the approach to have the VIOS insert
the MSS based on the size of the packets received off the wire, I think that
might be our best option...
Thanks,
Brian
>
> Regards
>
> Jon
>
>>
>> Unfortunately, I don't think we'd have a good way to get gso_segs set correctly as I don't
>> see how that would get passed back up the interface.
>>
>> Thanks,
>>
>> Brian
>>
>>
>> --
>> Brian King
>> Power Linux I/O
>> IBM Linux Technology Center
>>
>
--
Brian King
Power Linux I/O
IBM Linux Technology Center
^ permalink raw reply
* Re: [Qemu-devel] virsh dump (qemu guest memory dump?): KASLR enabled linux guest support
From: Daniel P. Berrange @ 2016-11-09 16:01 UTC (permalink / raw)
To: Laszlo Ersek
Cc: Andrew Jones, Dave Young, qiaonuohan, bhe, anderson, qemu-devel
In-Reply-To: <b49d5ff3-383e-245d-6d12-34c999d3cf0b@redhat.com>
On Wed, Nov 09, 2016 at 04:38:36PM +0100, Laszlo Ersek wrote:
> On 11/09/16 15:47, Daniel P. Berrange wrote:
> >>> That doesn't help with trying to diagnose a crash during boot up, since
> >>> the guest agent isn't running till fairly late. I'm also concerned that
> >>> the QEMU guest agent is likely to be far from widely deployed in guests,
>
> I have no hard data, but from the recent Fedora and RHEL-7 guest
> installations I've done, it seems like qga is installed automatically.
> (Not sure if that's because Anaconda realizes it's installing the OS in
> a VM.) Once I made sure there was an appropriate virtio-serial config in
> the domain XMLs, I could talk to the agents (mainly for fstrim's sake)
> immediately.
I'm thinking about cloud deployment where people rarely use Anaconda
directly - they'll use a pre-built cloud image, or customize the basic
cloud image. Neither Fedora or Ubuntu include the qemu guest agent in
their cloud images AFAICT, so very few OpenStack deployments will have
QEMU guest agent in at this time.
Of course we could try to get distros to embed qemu guets agent by
default, but its not clear if we'd succeed, given how aggressive
they are at stripping stuff out to create the smallest practical
images.
Regards,
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://entangle-photo.org -o- http://search.cpan.org/~danberr/ :|
^ permalink raw reply
* sip helper doesn't match on calls to myself
From: Juergen Schmidt @ 2016-11-09 16:01 UTC (permalink / raw)
To: netfilter
Hello!
I've got the following problem: If I'm calling myself, the incoming call
isn't matched by the sip helper rules, but the rules w/o sip helper. I
would have expected, that both calls are matched by the sip helper rules.
In detail:
Given is an asterisk server, one extension (C610 IP with 3 phones, which
can handle 2 lines at the same time) and a trunk to the provider.
The extension calls the own number, which provides the following scenario:
outgoing call: extension -> asterisk -> provider
and
incoming call: provider -> asterisk -> extension
*Important*: the provider uses independent media servers. This means:
the signaling server and the media servers have different IP addresses.
The calls from asterisk to the extensions are not described here,
because there is no problem.
This means:
At the outgoing interface (ppp0) to the provider can be seen 4 legs, 2
of the outgoing call and 2 of the incoming call. The 2 legs of the
incoming call are not matched by the sip helper rules!
kernel is 4.4.26
iptables is v1.4.21
iptables is configured like this:
modprobe nf_conntrack_sip sip_direct_media=0
echo 0 > /proc/sys/net/netfilter/nf_conntrack_helper
# register sip helper
iptables -I OUTPUT -t raw -p udp -m udp -o ppp0 -d 217.0.22.0/23 -s
$IPLOCAL --dport 5060 -j CT --helper sip
iptables -I PREROUTING -t raw -p udp -m udp -i ppp0 s 217.0.22.0/23 -d
$IPLOCAL --sport 5060 -j CT --helper sip
# match packages in OUTPUT:
---------------------------
iptables -I OUTPUT -p udp -s $IPLOCAL -d 217.0.22.0/23 --sport 5060
--dport 5060 -j ACCEPT
# rtp (matched by the outgoing call)
iptables -A OUTPUT -p udp -s $IPLOCAL -d 217.0.0.0/13 --sport
30000:40000 -m conntrack --ctstate RELATED,ESTABLISHED -m helper
--helper sip -j ACCEPT
# why is this rule necessary for the incoming call when calling to self?
# matched by the incoming call
iptables -A internet-out -p udp -o ppp0 -s $IPLOCAL -d 217.0.0.0/13
--sport 30000:40000 -m conntrack --ctstate ESTABLISHED -j ACCEPT
match packages in INPUT:
------------------------
iptables -I INPUT -i ppp0 -p udp -s 217.0.22.0/23 -d $IPLOCAL --sport
5060 --dport 5060 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# rtp (matched by the outgoing call)
iptables -A internet-in -i ppp0 -p udp -s 217.0.0.0/13 -d $IPLOCAL
--dport 30000:40000 -m conntrack --ctstate RELATED,ESTABLISHED -m helper
--helper sip -j ACCEPT
# why is this rule necessary for the incoming call?
# matched by the incoming call
iptables -A internet-in -i ppp0 -p udp -s 217.0.0.0/13 -d $IPLOCAL
--dport 30000:40000 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
conntrack -L gives:
# Outgoing call (is matched as expected):
# rtp
udp 17 179 src=$IPLOCAL dst=217.0.4.103 sport=17188 dport=2016
src=217.0.4.103 dst=$IPLOCAL sport=2016 dport=17188 [ASSURED] mark=0 use=1
# rtcp
udp 17 175 src=$IPLOCAL dst=217.0.4.103 sport=17189 dport=2017
src=217.0.4.103 dst=$IPLOCAL sport=2017 dport=17189 [ASSURED] mark=0 use=1
# Incoming call - which is *not* matched by sip helper rules, but by the
conntrack rule without sip helper:
# rtp
udp 17 179 src=217.0.4.135 dst=$IPLOCAL sport=53254 dport=18900
src=$IPLOCAL dst=217.0.4.135 sport=18900 dport=53254 [ASSURED] mark=0 use=2
# rtcp
udp 17 175 src=217.0.4.135 dst=$IPLOCAL sport=53255 dport=18901
src=$IPLOCAL dst=217.0.4.135 sport=18901 dport=53255 [ASSURED] mark=0 use=1
I would be glad to get some hint why it behaves like this.
Thanks,
Juergen
^ permalink raw reply
* Re: [lustre-devel] [PATCH] staging: lustre: ldlm: pl_recalc time handling is wrong
From: Arnd Bergmann @ 2016-11-09 16:00 UTC (permalink / raw)
To: Dilger, Andreas
Cc: James Simmons, Greg Kroah-Hartman, devel@driverdev.osuosl.org,
Drokin, Oleg, Linux Kernel Mailing List, Lustre Development List
In-Reply-To: <4C1355CC-5CF8-4AB3-95F1-B356EC357D00@intel.com>
On Wednesday, November 9, 2016 3:50:29 AM CET Dilger, Andreas wrote:
> On Nov 7, 2016, at 19:47, James Simmons <jsimmons@infradead.org> wrote:
> >
> > The ldlm_pool field pl_recalc_time is set to the current
> > monotonic clock value but the interval period is calculated
> > with the wall clock. This means the interval period will
> > always be far larger than the pl_recalc_period, which is
> > just a small interval time period. The correct thing to
> > do is to use monotomic clock current value instead of the
> > wall clocks value when calculating recalc_interval_sec.
>
> It looks like this was introduced by commit 8f83409cf
> "staging/lustre: use 64-bit time for pl_recalc" but that patch changed
> get_seconds() to a mix of ktime_get_seconds() and ktime_get_real_seconds()
> for an unknown reason. It doesn't appear that there is any difference
> in overhead between the two (on 64-bit at least).
It was meant to use ktime_get_real_seconds() consistently, very sorry
about the mistake. I don't remember exactly how we got there, I assume
I had started out using ktime_get_seconds() and then moved to
ktime_get_real_seconds() later but missed the last three instances.
> Since the ldlm pool recalculation interval is actually driven in response to
> load on the server, it makes sense to use the "real" time instead of the
> monotonic time (if I understand correctly) if the client is in a VM that
> may periodically be blocked and "miss time" compared to the outside world.
> Using the "real" clock, the recalc_interval_sec will correctly reflect the
> actual elapsed time rather than just the number of ticks inside the VM.
>
> Is my understanding of these different clocks correct?
No, this is not the difference: monotonic and real time always tick
at exactly the same rate, the only difference is the starting point.
monotonic time starts at boot and can not be adjusted, while real
time is set to reflect the UTC time base and gets initialized
from the real time clock at boot, or using settimeofday(2) or
NTP later on.
In my changelog text, I wrote
keeping the 'real' instead of 'monotonic' time because of the
debug prints.
the intention here is simply to have the console log keep the
same numbers as "date +%s" for absolute values. The patch that
James suggested converting everything to ktime_get_seconds()
would result in the same intervals that have always been there
(until I broke it by using time domains inconsistently), but
would mean we could use a u32 type for pl_recalc_time and
pl_recalc_period because that doesn't overflow until 136 years
after booting. (signed time_t overflows 68 years after 1970,
i.e 2038).
Arnd
^ permalink raw reply
* [lustre-devel] [PATCH] staging: lustre: ldlm: pl_recalc time handling is wrong
From: Arnd Bergmann @ 2016-11-09 16:00 UTC (permalink / raw)
To: Dilger, Andreas
Cc: James Simmons, Greg Kroah-Hartman, devel@driverdev.osuosl.org,
Drokin, Oleg, Linux Kernel Mailing List, Lustre Development List
In-Reply-To: <4C1355CC-5CF8-4AB3-95F1-B356EC357D00@intel.com>
On Wednesday, November 9, 2016 3:50:29 AM CET Dilger, Andreas wrote:
> On Nov 7, 2016, at 19:47, James Simmons <jsimmons@infradead.org> wrote:
> >
> > The ldlm_pool field pl_recalc_time is set to the current
> > monotonic clock value but the interval period is calculated
> > with the wall clock. This means the interval period will
> > always be far larger than the pl_recalc_period, which is
> > just a small interval time period. The correct thing to
> > do is to use monotomic clock current value instead of the
> > wall clocks value when calculating recalc_interval_sec.
>
> It looks like this was introduced by commit 8f83409cf
> "staging/lustre: use 64-bit time for pl_recalc" but that patch changed
> get_seconds() to a mix of ktime_get_seconds() and ktime_get_real_seconds()
> for an unknown reason. It doesn't appear that there is any difference
> in overhead between the two (on 64-bit at least).
It was meant to use ktime_get_real_seconds() consistently, very sorry
about the mistake. I don't remember exactly how we got there, I assume
I had started out using ktime_get_seconds() and then moved to
ktime_get_real_seconds() later but missed the last three instances.
> Since the ldlm pool recalculation interval is actually driven in response to
> load on the server, it makes sense to use the "real" time instead of the
> monotonic time (if I understand correctly) if the client is in a VM that
> may periodically be blocked and "miss time" compared to the outside world.
> Using the "real" clock, the recalc_interval_sec will correctly reflect the
> actual elapsed time rather than just the number of ticks inside the VM.
>
> Is my understanding of these different clocks correct?
No, this is not the difference: monotonic and real time always tick
at exactly the same rate, the only difference is the starting point.
monotonic time starts at boot and can not be adjusted, while real
time is set to reflect the UTC time base and gets initialized
from the real time clock at boot, or using settimeofday(2) or
NTP later on.
In my changelog text, I wrote
keeping the 'real' instead of 'monotonic' time because of the
debug prints.
the intention here is simply to have the console log keep the
same numbers as "date +%s" for absolute values. The patch that
James suggested converting everything to ktime_get_seconds()
would result in the same intervals that have always been there
(until I broke it by using time domains inconsistently), but
would mean we could use a u32 type for pl_recalc_time and
pl_recalc_period because that doesn't overflow until 136 years
after booting. (signed time_t overflows 68 years after 1970,
i.e 2038).
Arnd
^ permalink raw reply
* [DRAFT RFC] PVHv2 interaction with physical devices
From: Roger Pau Monné @ 2016-11-09 15:59 UTC (permalink / raw)
To: xen-devel
Cc: Andrew Cooper, Kelly, Julien Grall, Paul Durrant, Jan Beulich,
Boris Ostrovsky, Zytaruk
Hello,
I'm attaching a draft of how a PVHv2 Dom0 is supposed to interact with
physical devices, and what needs to be done inside of Xen in order to
achieve it. Current draft is RFC because I'm quite sure I'm missing bits
that should be written down here. So far I've tried to describe what my
previous series attempted to do by adding a bunch of IO and memory space
handlers.
Please note that this document only applies to PVHv2 Dom0, it is not
applicable to untrusted domains that will need more handlers in order to
secure Xen and other domains running on the same system. The idea is that
this can be expanded to untrusted domains also in the long term, thus having
a single set of IO and memory handlers for passed-through devices.
Roger.
---8<---
This document describes how a PVHv2 Dom0 is supposed to interact with physical
devices.
Architecture
============
Purpose
-------
Previous Dom0 implementations have always used PIRQs (physical interrupts
routed over event channels) in order to receive events from physical devices.
This prevents Dom0 form taking advantage of new hardware virtualization
features, like posted interrupts or hardware virtualized local APIC. Also the
current device memory management in the PVH Dom0 implementation is lacking,
and might not support devices that have memory regions past the 4GB
boundary.
The new PVH implementation (PVHv2) should overcome the interrupt limitations by
providing the same interface that's used on bare metal (a local and IO APICs)
thus allowing the usage of advanced hardware assisted virtualization
techniques. This also aligns with the trend on the hardware industry to
move part of the emulation into the silicon itself.
In order to improve the mapping of device memory areas, Xen will have to
know of those devices in advance (before Dom0 tries to interact with them)
so that the memory BARs will be properly mapped into Dom0 memory map.
The following document describes the proposed interface and implementation
of all the logic needed in order to achieve the functionality described
above.
MMIO areas
==========
Overview
--------
On x86 systems certain regions of memory might be used in order to manage
physical devices on the system. Access to this areas is critical for a
PVH Dom0 in order to operate properly. Unlike previous PVH Dom0 implementation
(PVHv1) that was setup with identity mappings of all the holes and reserved
regions found in the memory map, this new implementation intents to map only
what's actually needed by the Dom0.
Low 1MB
-------
When booted with a legacy BIOS, the low 1MB contains firmware related data
that should be identity mapped to the Dom0. This include the EBDA, video
memory and possibly ROMs. All non RAM regions below 1MB will be identity
mapped to the Dom0 so that it can access this data freely.
ACPI regions
------------
ACPI regions will be identity mapped to the Dom0, this implies regions with
type 3 and 4 in the e820 memory map. Also, since some BIOS report incorrect
memory maps, the top-level tables discovered by Xen (as listed in the
{X/R}SDT) that are not on RAM regions will be mapped to Dom0.
PCI memory BARs
---------------
PCI devices discovered by Xen will have it's BARs scanned in order to detect
memory BARs, and those will be identity mapped to Dom0. Since BARs can be
freely moved by the Dom0 OS by writing to the appropriate PCI config space
register, Xen must trap those accesses and unmap the previous region and
map the new one as set by Dom0.
Limitations
-----------
- Xen needs to be aware of any PCI device before Dom0 tries to interact with
it, so that the MMIO regions are properly mapped.
Interrupt management
====================
Overview
--------
On x86 systems there are tree different mechanisms that can be used in order
to deliver interrupts: IO APIC, MSI and MSI-X. Note that each device might
support different methods, but those are never active at the same time.
Legacy PCI interrupts
---------------------
The only way to deliver legacy PCI interrupts to PVHv2 guests is using the
IO APIC, PVHv2 domains don't have an emulated PIC. As a consequence the ACPI
_PIC method must be set to APIC mode by the Dom0 OS.
Xen will always provide a single IO APIC, that will match the number of
possible GSIs of the underlying hardware. This is possible because ACPI
uses a system cookie in order to name interrupts, so the IO APIC device ID
or pin number is not used in _PTR methods.
XXX: is it possible to have more than 256 GSIs?
The binding between the underlying physical interrupt and the emulated
interrupt is performed when unmasking an IO APIC PIN, so writes to the
IOREDTBL registers that unset the mask bit will trigger this binding
and enable the interrupt.
MSI Interrupts
--------------
MSI interrupts are setup using the PCI config space, either the IO ports
or the memory mapped configuration area. This means that both spaces should
be trapped by Xen, in order to detect accesses to these registers and
properly emulate them.
Since the offset of the MSI registers is not fixed, Xen has to query the
PCI configuration space in order to find the offset of the PCI_CAP_ID_MSI,
and then setup the correct traps, which also vary depending on the
capabilities of the device. The following list contains the set of MSI
registers that Xen will trap, please take into account that some devices
might only implement a subset of those registers, so not all traps will
be used:
- Message control register (offset 2): Xen traps accesses to this register,
and stores the data written to it into an internal structure. When the OS
sets the MSI enable bit (offset 0) Xen will setup the configured MSI
interrupts and route them to the guest.
- Message address register (offset 4): writes and reads to this register are
trapped by Xen, and the value is stored into an internal structure. This is
later used when MSI are enabled in order to configure the vectors injected
to the guest. Writes to this register with MSI already enabled will cause
a reconfiguration of the binding of interrupts to the guest.
- Message data register (offset 8 or 12 if message address is 64bits): writes
and reads to this register are trapped by Xen, and the value is stored into
an internal structure. This is used when MSI are enabled in order to
configure the vector where the guests expects to receive those interrupts.
Writes to this register with MSI already enabled will cause a
reconfiguration of the binding of interrupts to the guest.
- Mask and pending bits: reads or writes to those registers are not trapped
by Xen.
MSI-X Interrupts
----------------
MSI-X in contrast with MSI has part of the configuration registers in the
PCI configuration space, while others reside inside of the memory BARs of the
device. So in this case Xen needs to setup traps for both the PCI
configuration space and two different memory regions. Xen has to query the
position of the MSI-X capability using the PCI_CAP_ID_MSIX, and setup a
handler in order to trap accesses to the different registers. Xen also has
to figure out the position of the MSI-X table and PBA, using the table BIR
and table offset, and the PBA BIR and PBA offset. Once those are known a
handler should also be setup in order to trap accesses to those memory
regions.
This is the list of MSI-X registers that are used in order to manage MSI-X
in the PCI configuration space:
- Message control: Xen should trap accesses to this register in order to
detect changes to the MSI-X enable field (bit 15). Changes to this bit
will trigger the setup of the MSI-X table entries configured. Writes
to the function mask bit will be passed-through to the underlying
register.
- Table offset, table BIR, PBA offset, PBA BIR: accesses to those registers
are not trapped by Xen.
The following registers reside in memory, and are pointed out by the Table and
PBA fields found in the PCI configuration space:
- Message address and data: writes and reads to those registers are trapped
by Xen, and the value is stored into an internal structure. This is later
used by Xen in order to configure the interrupt injected to the guest.
Writes to those registers with MSI-X already enabled will not cause a
reconfiguration of the interrupt.
- Vector control: writes and reads are trapped, clearing the mask bit (bit 0)
will cause Xen to setup the configured interrupt if MSI-X is globally
enabled in the message control field.
- Pending bits array: writes and reads to this register are not trapped by
Xen.
Limitations
-----------
- Due to the fact that Dom0 is not able to parse dynamic ACPI tables,
some UART devices might only function in polling mode, because Xen
will be unable to properly configure the interrupt pins without Dom0
collaboration, and the UART in use by Xen should be explicitly blacklisted
from Dom0 access.
_______________________________________________
Xen-devel mailing list
Xen-devel@lists.xen.org
https://lists.xen.org/xen-devel
^ permalink raw reply
* [ppdev] sysfs warning on qemu boot
From: Joe Lawrence @ 2016-11-09 16:00 UTC (permalink / raw)
To: linux-kernel@vger.kernel.org; +Cc: Sudip Mukherjee
Hi Sudip,
I hit a sysfs_warn_dup inside QEMU running 4.9.0-rc4 (I suspect earlier
versions as well, but this is the first upstream I've run in a while).
This warning looks like something the kernel test robot ran into a few
months ago:
http://marc.info/?t=147267773300003&r=1&w=2
I'm running CentOS7 inside QEMU with the following options:
qemu-system-x86_64 -smp 4 -m 8192 \
-drive file=centos7.qcow2,cache=none,if=virtio \
-usbdevice tablet -enable-kvm -monitor stdio \
-parallel /dev/null -redir tcp:2222::22 \
-serial file:serial.out
a kernel with these options:
% grep PARPORT .config
CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y
CONFIG_PARPORT=m
CONFIG_PARPORT_PC=m
# CONFIG_PARPORT_SERIAL is not set
# CONFIG_PARPORT_PC_FIFO is not set
# CONFIG_PARPORT_PC_SUPERIO is not set
# CONFIG_PARPORT_GSC is not set
# CONFIG_PARPORT_AX88796 is not set
CONFIG_PARPORT_1284=y
# CONFIG_I2C_PARPORT is not set
# CONFIG_I2C_PARPORT_LIGHT is not set
and I see the following warning on boot:
ppdev: user-space parallel port driver
------------[ cut here ]------------
WARNING: CPU: 2 PID: 581 at fs/sysfs/dir.c:31 sysfs_warn_dup+0x64/0x80
sysfs: cannot create duplicate filename '/devices/pnp0/00:04/ppdev/parport0'
Modules linked in: ppdev sg parport_pc(+) parport pcspkr i2c_piix4 ip_tables xfs libcrc32c sr_mod cdrom ata_generic pata_acpi cirrus drm_kms_helper syscopyarea sysfillrect sysimgblt fb_sys_fops ttm drm virtio_blk e1000 virtio_pci ata_piix virtio_ring i2c_core libata virtio serio_raw floppy dm_mirror dm_region_hash dm_log dm_mod
CPU: 2 PID: 581 Comm: systemd-udevd Not tainted 4.9.0-rc4+ #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 0.5.1 01/01/2011
ffffc900014c76f8 ffffffff81351b2f ffffc900014c7748 0000000000000000
ffffc900014c7738 ffffffff8108bab1 0000001f00001000 ffff8802280bc548
ffff88022c290c68 ffff880225c98398 ffff88022d834bd8 0000000006300000
Call Trace:
[<ffffffff81351b2f>] dump_stack+0x63/0x84
[<ffffffff8108bab1>] __warn+0xd1/0xf0
[<ffffffff8108bb2f>] warn_slowpath_fmt+0x5f/0x80
[<ffffffff812a3350>] ? kernfs_path_from_node+0x50/0x60
[<ffffffff812a6b44>] sysfs_warn_dup+0x64/0x80
[<ffffffff812a6c2e>] sysfs_create_dir_ns+0x7e/0x90
[<ffffffff81354ad2>] kobject_add_internal+0xa2/0x320
[<ffffffff8148ff33>] ? device_private_init+0x23/0x70
[<ffffffff81354f85>] kobject_add+0x75/0xd0
[<ffffffff816d8002>] ? mutex_lock+0x12/0x2f
[<ffffffff81490099>] device_add+0x119/0x610
[<ffffffff81490788>] device_create_groups_vargs+0xd8/0x100
[<ffffffffa0316090>] ? dead_read+0x10/0x10 [parport]
[<ffffffff81490821>] device_create+0x51/0x70
[<ffffffff811fd5a1>] ? kfree+0x121/0x170
[<ffffffff816ccf60>] ? klist_next+0x20/0xf0
[<ffffffffa03230b4>] pp_attach+0x34/0x40 [ppdev]
[<ffffffffa03160a7>] driver_check+0x17/0x20 [parport]
[<ffffffff814913e8>] bus_for_each_drv+0x68/0xb0
[<ffffffffa03162b9>] attach_driver_chain+0x59/0x60 [parport]
[<ffffffffa0316720>] parport_announce_port+0xc0/0x110 [parport]
[<ffffffffa032ae7c>] parport_pc_probe_port+0x70c/0xb20 [parport_pc]
[<ffffffff812a460a>] ? kernfs_activate+0x7a/0xe0
[<ffffffff8148f6cc>] ? _dev_info+0x6c/0x90
[<ffffffffa032bd95>] parport_pc_pnp_probe+0x145/0x1e0 [parport_pc]
[<ffffffffa032bc50>] ? sio_via_probe+0x430/0x430 [parport_pc]
[<ffffffff8140c8c1>] pnp_device_probe+0x61/0xc0
[<ffffffff814936e7>] driver_probe_device+0x227/0x440
[<ffffffff814939dd>] __driver_attach+0xdd/0xe0
[<ffffffff81493900>] ? driver_probe_device+0x440/0x440
[<ffffffff8149130c>] bus_for_each_dev+0x6c/0xc0
[<ffffffff81492fae>] driver_attach+0x1e/0x20
[<ffffffff814928c5>] bus_add_driver+0x45/0x270
[<ffffffff81494450>] driver_register+0x60/0xe0
[<ffffffff8140c710>] pnp_register_driver+0x20/0x30
[<ffffffffa0332398>] parport_pc_init+0x2b5/0xf1d [parport_pc]
[<ffffffffa03320e3>] ? parport_parse_param.constprop.15+0xe3/0xe3 [parport_pc]
[<ffffffff81002190>] do_one_initcall+0x50/0x190
[<ffffffff811958ce>] ? do_init_module+0x27/0x1f1
[<ffffffff811fc3bc>] ? kmem_cache_alloc_trace+0x16c/0x1b0
[<ffffffff81195907>] do_init_module+0x60/0x1f1
[<ffffffff81116e7b>] load_module+0x15ab/0x1aa0
[<ffffffff81113810>] ? __symbol_put+0x60/0x60
[<ffffffff812f7b1d>] ? ima_post_read_file+0x3d/0x80
[<ffffffff812d39eb>] ? security_kernel_post_read_file+0x6b/0x80
[<ffffffff81117586>] SYSC_finit_module+0xa6/0xf0
[<ffffffff811175ee>] SyS_finit_module+0xe/0x10
[<ffffffff816da837>] entry_SYSCALL_64_fastpath+0x1a/0xa9
---[ end trace 8304e3df5abed4a7 ]---
------------[ cut here ]------------
WARNING: CPU: 2 PID: 581 at lib/kobject.c:240 kobject_add_internal+0x2b5/0x320
kobject_add_internal failed for parport0 with -EEXIST, don't try to register things with the same name in the same directory.
Modules linked in: ppdev sg parport_pc(+) parport pcspkr i2c_piix4 ip_tables xfs libcrc32c sr_mod cdrom ata_generic pata_acpi cirrus drm_kms_helper syscopyarea sysfillrect sysimgblt fb_sys_fops ttm drm virtio_blk e1000 virtio_pci ata_piix virtio_ring i2c_core libata virtio serio_raw floppy dm_mirror dm_region_hash dm_log dm_mod
CPU: 2 PID: 581 Comm: systemd-udevd Tainted: G W 4.9.0-rc4+ #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 0.5.1 01/01/2011
ffffc900014c7748 ffffffff81351b2f ffffc900014c7798 0000000000000000
ffffc900014c7788 ffffffff8108bab1 000000f02c290c68 ffff880226a0afe8
ffff88022d834bd8 00000000ffffffef ffff88022d834bd8 0000000006300000
Call Trace:
[<ffffffff81351b2f>] dump_stack+0x63/0x84
[<ffffffff8108bab1>] __warn+0xd1/0xf0
[<ffffffff8108bb2f>] warn_slowpath_fmt+0x5f/0x80
[<ffffffff81354ce5>] kobject_add_internal+0x2b5/0x320
[<ffffffff8148ff33>] ? device_private_init+0x23/0x70
[<ffffffff81354f85>] kobject_add+0x75/0xd0
[<ffffffff816d8002>] ? mutex_lock+0x12/0x2f
[<ffffffff81490099>] device_add+0x119/0x610
[<ffffffff81490788>] device_create_groups_vargs+0xd8/0x100
[<ffffffffa0316090>] ? dead_read+0x10/0x10 [parport]
[<ffffffff81490821>] device_create+0x51/0x70
[<ffffffff811fd5a1>] ? kfree+0x121/0x170
[<ffffffff816ccf60>] ? klist_next+0x20/0xf0
[<ffffffffa03230b4>] pp_attach+0x34/0x40 [ppdev]
[<ffffffffa03160a7>] driver_check+0x17/0x20 [parport]
[<ffffffff814913e8>] bus_for_each_drv+0x68/0xb0
[<ffffffffa03162b9>] attach_driver_chain+0x59/0x60 [parport]
[<ffffffffa0316720>] parport_announce_port+0xc0/0x110 [parport]
[<ffffffffa032ae7c>] parport_pc_probe_port+0x70c/0xb20 [parport_pc]
[<ffffffff812a460a>] ? kernfs_activate+0x7a/0xe0
[<ffffffff8148f6cc>] ? _dev_info+0x6c/0x90
[<ffffffffa032bd95>] parport_pc_pnp_probe+0x145/0x1e0 [parport_pc]
[<ffffffffa032bc50>] ? sio_via_probe+0x430/0x430 [parport_pc]
[<ffffffff8140c8c1>] pnp_device_probe+0x61/0xc0
[<ffffffff814936e7>] driver_probe_device+0x227/0x440
[<ffffffff814939dd>] __driver_attach+0xdd/0xe0
[<ffffffff81493900>] ? driver_probe_device+0x440/0x440
[<ffffffff8149130c>] bus_for_each_dev+0x6c/0xc0
[<ffffffff81492fae>] driver_attach+0x1e/0x20
[<ffffffff814928c5>] bus_add_driver+0x45/0x270
[<ffffffff81494450>] driver_register+0x60/0xe0
[<ffffffff8140c710>] pnp_register_driver+0x20/0x30
[<ffffffffa0332398>] parport_pc_init+0x2b5/0xf1d [parport_pc]
[<ffffffffa03320e3>] ? parport_parse_param.constprop.15+0xe3/0xe3 [parport_pc]
[<ffffffff81002190>] do_one_initcall+0x50/0x190
[<ffffffff811958ce>] ? do_init_module+0x27/0x1f1
[<ffffffff811fc3bc>] ? kmem_cache_alloc_trace+0x16c/0x1b0
[<ffffffff81195907>] do_init_module+0x60/0x1f1
[<ffffffff81116e7b>] load_module+0x15ab/0x1aa0
[<ffffffff81113810>] ? __symbol_put+0x60/0x60
[<ffffffff812f7b1d>] ? ima_post_read_file+0x3d/0x80
[<ffffffff812d39eb>] ? security_kernel_post_read_file+0x6b/0x80
[<ffffffff81117586>] SYSC_finit_module+0xa6/0xf0
[<ffffffff811175ee>] SyS_finit_module+0xe/0x10
[<ffffffff816da837>] entry_SYSCALL_64_fastpath+0x1a/0xa9
---[ end trace 8304e3df5abed4a8 ]---
I can repeat the warnings by unloading and reloading the parallel port
drivers:
rmmod parport_pc ppdev parport
modprobe parport_pc
Instrumenting sysfs_create_dir_ns when adding
'/devices/pnp0/00:04/ppdev/parport0', I see one call from ppdev/parport
drivers:
entry_SYSCALL_64_fastpath
SyS_finit_module
SYSC_finit_module
load_module
do_init_module
do_one_initcall
ppdev_init [ppdev]
__parport_register_driver [parport]
bus_for_each_dev
port_check[parport]
pp_attach[ppdev]
device_create
device_create_groups_vargs
device_add
kobject_add
kobject_add_internal
sysfs_create_dir_ns
and then a follow up call to add the same sysfs directory via the
parport_pc driver:
entry_SYSCALL_64_fastpath
SyS_finit_module
SYSC_finit_module
load_module
do_init_module
do_one_initcall
parport_pc_init [parport_pc]
pnp_register_driver
driver_register
bus_add_driver
driver_attach
bus_for_each_dev
__driver_attach
driver_probe_device
pnp_device_probe
parport_pc_pnp_probe [parport_pc]
parport_pc_probe_port [parport_pc]
parport_announce_port [parport]
attach_driver_chain [parport]
bus_for_each_drv
driver_check [parport]
pp_attach[ppdev]
device_create
device_create_groups_vargs
device_add
kobject_add
kobject_add_internal
sysfs_create_dir_ns
This warning is readily reproducible in my current VM setup, so I can
test patches or further debugging if necessary.
Hope this helps,
-- Joe
^ permalink raw reply
* [PATCH] fpga zynq: Check the bitstream for validity
From: Jason Gunthorpe @ 2016-11-09 16:00 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <ab7684fe-2407-330e-9001-21dccc781983@suse.com>
On Wed, Nov 09, 2016 at 04:18:29PM +0100, Matthias Brugger wrote:
> I get your point. Especially looping to the whole file to find the sync
> header can take a while, especially if the file is big and the sync header
> is not present.
Er, no not at all. If you send garbage to the FPGA the driver detects
it after a 2.5s timeout. The memory scan is always alot faster.
In the normal success case it is ~5 compares.
> So I think the whole idea behind this patch is to provide feedback to the
> user about what went wrong when trying to update the FPGA. Is there a way to
> get this information from the hardware which discards the update?
No, not with such specificity.
Jason
^ permalink raw reply
* [PATCH] fpga zynq: Check the bitstream for validity
From: Jason Gunthorpe @ 2016-11-09 15:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <609bc971-bb6d-8cd2-8e64-23c0082a6216@topic.nl>
On Wed, Nov 09, 2016 at 03:21:52PM +0100, Mike Looijmans wrote:
> I think the basic idea behind the commit is flawed to begin with and the
> patch should be discarded completely. The same discussion was done for the
> Xilinx FPGA manager driver, which resulted in the decision that the tooling
> should create a proper file.
That hasn't changed at all, this just produces a clear and obvious
message about what is wrong instead of 'timed out'.
Remember, again, the Xilinx tools do not produce an acceptable
bitstream file by default. You need to do very strange and special
things to get something acceptable to this driver. It is a very easy
mistake to make and hard to track down if you don't already know these
details.
> This driver should either become obsolete or at least move into the
> same direction as the FPGA manager rather than away from that.
I don't understand what you are talking about here, this is a fpga mgr
driver already, and is consistent with the FPGA manger - the auto
detect stuff was removed a while ago and isn't coming back.
It is perfectly reasonable for fpga manager drivers to pre-validate
the proposed bitstream, IMHO all of the drivers should try and do
that step.
Remember, it is deeply unlikely but sending garbage to an FPGA could
damage it.
> Besides political arguments, there's a more pressing technical argument
> against this theck as well: The whole check is pointless since the hardware
> itself already verifies the validity of the stream.
The purpose of the check is not to protect the hardware. The check is
to help the user provide the correct input because the hardware
provides no feedback at all on what is wrong with the input.
And again, the out-of-tree Xilinx driver accepted files that this
driver does not, so having a clear and understandable message is going
to be very important for users.
> doesn't have any effect, the hardware will discard it. There's no reason to
> waste CPU cycles duplicating this check in software.
In the typical success case we are talking about 5 32 bit compares,
which isn't even worth considering.
Jason
^ permalink raw reply
* Re: [RFC] qtn: add FullMAC firmware for Quantenna QSR10G wifi device
From: Johannes Berg @ 2016-11-09 15:56 UTC (permalink / raw)
To: igor.mitsyanko.os
Cc: linux-wireless, btherthala, hwang, smaksimenko, dlebed,
Igor Mitsyanko, Kamlesh Rath, Sergey Matyukevich, Avinash Patil
In-Reply-To: <1478700000-11624-1-git-send-email-igor.mitsyanko.os@quantenna.com>
On 2016-11-09 14:59, igor.mitsyanko.os@quantenna.com wrote:
>
> [...]
So I'm told this thing actually runs a full Linux, and consequently is
(or at least contains) GPL code.
With the carl9170 driver, the GPL firmware was required to be put as
buildable source code into the linux-firmware tree.
What should happen here? I expect the sources for this to be pretty big
unlike carl9170 (1.1MB source).
johannes
^ permalink raw reply
* Re: [PATCH v2 2/2] mm: hugetlb: support gigantic surplus pages
From: Gerald Schaefer @ 2016-11-09 15:55 UTC (permalink / raw)
To: Huang Shijie
Cc: akpm, catalin.marinas, n-horiguchi, mhocko, kirill.shutemov,
aneesh.kumar, mike.kravetz, linux-mm, will.deacon, steve.capper,
kaly.xin, nd, linux-arm-kernel
In-Reply-To: <1478675294-2507-1-git-send-email-shijie.huang@arm.com>
On Wed, 9 Nov 2016 15:08:14 +0800
Huang Shijie <shijie.huang@arm.com> wrote:
> When testing the gigantic page whose order is too large for the buddy
> allocator, the libhugetlbfs test case "counter.sh" will fail.
>
> The failure is caused by:
> 1) kernel fails to allocate a gigantic page for the surplus case.
> And the gather_surplus_pages() will return NULL in the end.
>
> 2) The condition checks for "over-commit" is wrong.
>
> This patch adds code to allocate the gigantic page in the
> __alloc_huge_page(). After this patch, gather_surplus_pages()
> can return a gigantic page for the surplus case.
>
> This patch changes the condition checks for:
> return_unused_surplus_pages()
> nr_overcommit_hugepages_store()
> hugetlb_overcommit_handler()
>
> This patch also set @nid with proper value when NUMA_NO_NODE is
> passed to alloc_gigantic_page().
>
> After this patch, the counter.sh can pass for the gigantic page.
>
> Acked-by: Steve Capper <steve.capper@arm.com>
> Signed-off-by: Huang Shijie <shijie.huang@arm.com>
> ---
> 1. fix the wrong check in hugetlb_overcommit_handler();
> 2. try to fix the s390 issue.
> ---
> mm/hugetlb.c | 20 ++++++++++++++------
> 1 file changed, 14 insertions(+), 6 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 9fdfc24..5dbfd62 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -1095,6 +1095,9 @@ static struct page *alloc_gigantic_page(int nid, unsigned int order)
> unsigned long ret, pfn, flags;
> struct zone *z;
>
> + if (nid == NUMA_NO_NODE)
> + nid = numa_mem_id();
> +
Now counter.sh works (on s390) w/o the lockdep warning. However, it looks
like this change will now result in inconsistent behavior compared to the
normal sized hugepages, regarding surplus page allocation. Setting nid to
numa_mem_id() means that only the node of the current CPU will be considered
for allocating a gigantic page, as opposed to just "preferring" the current
node in the normal size case (__hugetlb_alloc_buddy_huge_page() ->
alloc_pages_node()) with a fallback to using other nodes.
I am not really familiar with NUMA, and I might be wrong here, but if
this is true then gigantic pages, which may be hard allocate at runtime
in general, will be even harder to find (as surplus pages) because you
only look on the current node.
I honestly do not understand why alloc_gigantic_page() needs a nid
parameter at all, since it looks like it will only be called from
alloc_fresh_gigantic_page_node(), which in turn is only called
from alloc_fresh_gigantic_page() in a "for_each_node" loop (at least
before your patch).
Now it could be an option to also use alloc_fresh_gigantic_page()
in your patch, instead of directly calling alloc_gigantic_page(),
in __alloc_huge_page(). This would fix the "local node only" issue,
but I am not sure how to handle the required nodes_allowed parameter.
Maybe someone with more NUMA insight could have a look at this.
The patch as it is also seems to work, with the "local node only"
restriction, so it may be an option to just accept this restriction.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
^ permalink raw reply
* RE: Shouldn't VFIO virtualize the ATS capability?
From: Ilya Lesokhin @ 2016-11-09 15:55 UTC (permalink / raw)
To: Alex Williamson
Cc: linux-pci@vger.kernel.org, kvm@vger.kernel.org,
bhelgaas@google.com, Adi Menachem
In-Reply-To: <20161109085326.62b47d2f@t450s.home>
I guess hiding it is even better.
> -----Original Message-----
> From: Alex Williamson [mailto:alex.williamson@redhat.com]
> Sent: Wednesday, November 09, 2016 5:53 PM
> To: Ilya Lesokhin <ilyal@mellanox.com>
> Cc: linux-pci@vger.kernel.org; kvm@vger.kernel.org; bhelgaas@google.com;
> Adi Menachem <adim@mellanox.com>
> Subject: Re: Shouldn't VFIO virtualize the ATS capability?
>
> On Wed, 9 Nov 2016 15:25:16 +0000
> Ilya Lesokhin <ilyal@mellanox.com> wrote:
>
> > > -----Original Message-----
> > > From: Alex Williamson [mailto:alex.williamson@redhat.com]
> > > Sent: Wednesday, November 09, 2016 5:08 PM
> > > To: Ilya Lesokhin <ilyal@mellanox.com>
> > > Cc: linux-pci@vger.kernel.org; kvm@vger.kernel.org;
> > > bhelgaas@google.com; Adi Menachem <adim@mellanox.com>
> > > Subject: Re: Shouldn't VFIO virtualize the ATS capability?
> > >
> > > On Wed, 9 Nov 2016 14:49:02 +0000
> > > Ilya Lesokhin <ilyal@mellanox.com> wrote:
> > >
> > > > I would virtualize the "ATS Control Register".
> > >
> > > And do what?
> > I think it should be read only.
>
> That would violate the spec, in which case it shouldn't be virtualized, the
> capability should be hidden.
>
> > > > Regarding poor behavior, I couldn't really find what happens when
> > > > ATS is
> > > misconfigured, but I would assume it can cause problems.
> > > > The scenarios I'm concerned about are:
> > > > 1. The guest enables translation caching, while the hypervisor
> > > > thinks
> > > there are disabled -> Hypervisor won't issue invalidations.
> > >
> > > Aren't invalidations issued by the iommu, why does the hypervisor
> > > need to participate? How would a software entity induce an
> invalidation?
> > That's what one might expect from a sane design, but
> > http://lxr.free-electrons.com/source/drivers/iommu/intel-iommu.c?v=4.8
> > #L1549
> > seems to imply otherwise :(
> > >
> > > > 2. Smallest Translation Unit misconfiguration. Not sure if it
> > > > will cause
> > > invalid access or only poor caching behavior.
> > > >
> > > > Thanks,
> > > > Ilya
> > > >
> > > > > -----Original Message-----
> > > > > From: Alex Williamson [mailto:alex.williamson@redhat.com]
> > > > > Sent: Sunday, November 06, 2016 7:09 PM
> > > > > To: Ilya Lesokhin <ilyal@mellanox.com>
> > > > > Cc: linux-pci@vger.kernel.org; kvm@vger.kernel.org;
> > > > > bhelgaas@google.com
> > > > > Subject: Re: Shouldn't VFIO virtualize the ATS capability?
> > > > >
> > > > > On Sun, 6 Nov 2016 11:13:09 +0000 Ilya Lesokhin
> > > > > <ilyal@mellanox.com> wrote:
> > > > >
> > > > > > Hi
> > > > > > I've noticed that VFIO doesn't virtualize the ATS capability.
> > > > > > It seems to me that translation caching and Smallest
> > > > > > Translation Unit is
> > > > > something you would want to control on the host. Am I wrong?
> > > > >
> > > > > What about those fields would we virtualize? Why does the host
> > > > > need to be an intermediary? Can the user induce poor behavior
> > > > > with direct access to them? Thanks,
> > > > >
> > > > > Alex
> >
^ permalink raw reply
* [PATCH v2 2/2] mm: hugetlb: support gigantic surplus pages
From: Gerald Schaefer @ 2016-11-09 15:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1478675294-2507-1-git-send-email-shijie.huang@arm.com>
On Wed, 9 Nov 2016 15:08:14 +0800
Huang Shijie <shijie.huang@arm.com> wrote:
> When testing the gigantic page whose order is too large for the buddy
> allocator, the libhugetlbfs test case "counter.sh" will fail.
>
> The failure is caused by:
> 1) kernel fails to allocate a gigantic page for the surplus case.
> And the gather_surplus_pages() will return NULL in the end.
>
> 2) The condition checks for "over-commit" is wrong.
>
> This patch adds code to allocate the gigantic page in the
> __alloc_huge_page(). After this patch, gather_surplus_pages()
> can return a gigantic page for the surplus case.
>
> This patch changes the condition checks for:
> return_unused_surplus_pages()
> nr_overcommit_hugepages_store()
> hugetlb_overcommit_handler()
>
> This patch also set @nid with proper value when NUMA_NO_NODE is
> passed to alloc_gigantic_page().
>
> After this patch, the counter.sh can pass for the gigantic page.
>
> Acked-by: Steve Capper <steve.capper@arm.com>
> Signed-off-by: Huang Shijie <shijie.huang@arm.com>
> ---
> 1. fix the wrong check in hugetlb_overcommit_handler();
> 2. try to fix the s390 issue.
> ---
> mm/hugetlb.c | 20 ++++++++++++++------
> 1 file changed, 14 insertions(+), 6 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 9fdfc24..5dbfd62 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -1095,6 +1095,9 @@ static struct page *alloc_gigantic_page(int nid, unsigned int order)
> unsigned long ret, pfn, flags;
> struct zone *z;
>
> + if (nid == NUMA_NO_NODE)
> + nid = numa_mem_id();
> +
Now counter.sh works (on s390) w/o the lockdep warning. However, it looks
like this change will now result in inconsistent behavior compared to the
normal sized hugepages, regarding surplus page allocation. Setting nid to
numa_mem_id() means that only the node of the current CPU will be considered
for allocating a gigantic page, as opposed to just "preferring" the current
node in the normal size case (__hugetlb_alloc_buddy_huge_page() ->
alloc_pages_node()) with a fallback to using other nodes.
I am not really familiar with NUMA, and I might be wrong here, but if
this is true then gigantic pages, which may be hard allocate at runtime
in general, will be even harder to find (as surplus pages) because you
only look on the current node.
I honestly do not understand why alloc_gigantic_page() needs a nid
parameter at all, since it looks like it will only be called from
alloc_fresh_gigantic_page_node(), which in turn is only called
from alloc_fresh_gigantic_page() in a "for_each_node" loop (at least
before your patch).
Now it could be an option to also use alloc_fresh_gigantic_page()
in your patch, instead of directly calling alloc_gigantic_page(),
in __alloc_huge_page(). This would fix the "local node only" issue,
but I am not sure how to handle the required nodes_allowed parameter.
Maybe someone with more NUMA insight could have a look at this.
The patch as it is also seems to work, with the "local node only"
restriction, so it may be an option to just accept this restriction.
^ permalink raw reply
* [meta-oe][PATCH 2/2] asio: fix a musl compilation warning
From: André Draszik @ 2016-11-09 15:55 UTC (permalink / raw)
To: openembedded-devel
In-Reply-To: <20161109155512.10972-1-git@andred.net>
From: André Draszik <adraszik@tycoint.com>
This is the same patch as applied to boost's copy
of asio in openembedded-core, with slightly
massaged paths
Signed-off-by: André Draszik <adraszik@tycoint.com>
Reviewed-by: Sylvain Lemieux <slemieux@tycoint.com>
---
...01-use-POSIX-poll.h-instead-of-sys-poll.h.patch | 64 ++++++++++++++++++++++
meta-oe/recipes-support/asio/asio_1.10.6.bb | 5 +-
2 files changed, 68 insertions(+), 1 deletion(-)
create mode 100644 meta-oe/recipes-support/asio/asio/0001-use-POSIX-poll.h-instead-of-sys-poll.h.patch
diff --git a/meta-oe/recipes-support/asio/asio/0001-use-POSIX-poll.h-instead-of-sys-poll.h.patch b/meta-oe/recipes-support/asio/asio/0001-use-POSIX-poll.h-instead-of-sys-poll.h.patch
new file mode 100644
index 0000000..7f95f5a
--- /dev/null
+++ b/meta-oe/recipes-support/asio/asio/0001-use-POSIX-poll.h-instead-of-sys-poll.h.patch
@@ -0,0 +1,64 @@
+From dac36a170188917e2f61b0394ba8a2f6509ddf3a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Andr=C3=A9=20Draszik?= <adraszik@tycoint.com>
+Date: Tue, 8 Nov 2016 20:39:55 +0000
+Subject: [PATCH] use POSIX poll.h instead of sys/poll.h
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+POSIX specifies that <poll.h> is the correct header to
+include for poll()
+ http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html
+whereas <sys/poll.h> is only needed for ancient glibc (<2.3),
+so let's follow POSIX instead.
+
+As a side-effect, this silences numerous compilation warnings
+when compiling against the musl C-library:
+
+In file included from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/socket_types.hpp:57:0,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/impl/error_code.ipp:29,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/error_code.hpp:185,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/throw_error.hpp:19,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/impl/posix_tss_ptr.ipp:23,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/posix_tss_ptr.hpp:74,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/tss_ptr.hpp:27,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/call_stack.hpp:20,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/impl/handler_alloc_hook.ipp:19,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/handler_alloc_hook.hpp:78,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/handler_alloc_helpers.hpp:21,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/bind_handler.hpp:19,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/detail/wrapped_handler.hpp:18,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/io_service.hpp:24,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/basic_io_object.hpp:19,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/basic_socket.hpp:20,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio/basic_datagram_socket.hpp:20,
+ from ../../../../asio-1.10.6/src/examples/cpp03/../../../include/asio.hpp:19,
+ from ../../../../asio-1.10.6/src/examples/cpp03/buffers/reference_counted.cpp:11:
+<sysroot>/usr/include/sys/poll.h:1:2: warning: #warning redirecting incorrect #include <sys/poll.h> to <poll.h> [-Wcpp]
+ #warning redirecting incorrect #include <sys/poll.h> to <poll.h>
+ ^~~~~~~
+
+etc.
+
+Signed-off-by: André Draszik <adraszik@tycoint.com>
+---
+Upstream-Status: Submitted https://svn.boost.org/trac/boost/ticket/12419
+ include/asio/detail/socket_types.hpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/include/asio/detail/socket_types.hpp b/include/asio/detail/socket_types.hpp
+index f2600c2..cb61b8e 100644
+--- a/include/asio/detail/socket_types.hpp
++++ b/include/asio/detail/socket_types.hpp
+@@ -54,7 +54,7 @@
+ #else
+ # include <sys/ioctl.h>
+ # if !defined(__SYMBIAN32__)
+-# include <sys/poll.h>
++# include <poll.h>
+ # endif
+ # include <sys/types.h>
+ # include <sys/stat.h>
+--
+2.10.2
+
diff --git a/meta-oe/recipes-support/asio/asio_1.10.6.bb b/meta-oe/recipes-support/asio/asio_1.10.6.bb
index d6f4ddd..5656039 100644
--- a/meta-oe/recipes-support/asio/asio_1.10.6.bb
+++ b/meta-oe/recipes-support/asio/asio_1.10.6.bb
@@ -5,4 +5,7 @@ LIC_FILES_CHKSUM = "file://COPYING;md5=fede5286a78559dd646e355ab0cc8f04"
SRC_URI[md5sum] = "85d014a356a6e004cd30ccd4c9b6a5c2"
SRC_URI[sha256sum] = "e0d71c40a7b1f6c1334008fb279e7361b32a063e020efd21e40d9d8ff037195e"
-SRC_URI += "file://0001-Automatically-handle-glibc-variant-of-strerror_r-wit.patch"
+SRC_URI += "\
+ file://0001-Automatically-handle-glibc-variant-of-strerror_r-wit.patch \
+ file://0001-use-POSIX-poll.h-instead-of-sys-poll.h.patch \
+"
--
2.10.2
^ permalink raw reply related
* [meta-oe][PATCH 1/2] asio: DEPENDS on openssl
From: André Draszik @ 2016-11-09 15:55 UTC (permalink / raw)
To: openembedded-devel
From: André Draszik <adraszik@tycoint.com>
asio may or may not build examples and tests that
depend on OpenSSL, alas, it has no way to explicitly
enable or disable OpenSSL support, which is enabled
unconditionally whenever openssl/ssl.h can be found.
Due to that we get non-deterministic build behaviour,
based on whether or not some other package pulled
OpenSSL into the sysroot before asio's configure is
running.
Additionally, we can get random compilation failures
if openssl/ssl.h exists during configure time, but is
removed from sysroot later, e.g. due to a concurrent
rebuild of OpenSSL at the same time as building asio.
Having an explicit DEPENDS avoids both these problems.
We can not use PACKAGECONFIG, because as mentioned
above there is no way to explicitly disable OpenSSL
support.
Signed-off-by: André Draszik <adraszik@tycoint.com>
Reviewed-by: Sylvain Lemieux <slemieux@tycoint.com>
---
meta-oe/recipes-support/asio/asio.inc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/meta-oe/recipes-support/asio/asio.inc b/meta-oe/recipes-support/asio/asio.inc
index 9cea824..54f78e4 100644
--- a/meta-oe/recipes-support/asio/asio.inc
+++ b/meta-oe/recipes-support/asio/asio.inc
@@ -6,7 +6,7 @@ HOMEPAGE = "http://think-async.com/Asio"
SECTION = "libs"
LICENSE = "BSL-1.0"
-DEPENDS = "boost"
+DEPENDS = "boost openssl"
SRC_URI = "${SOURCEFORGE_MIRROR}/asio/${BP}.tar.bz2"
--
2.10.2
^ permalink raw reply related
* Re: [Qemu-devel] [PATCH] vhost: Use vbus var instead of VIRTIO_BUS() macro
From: Paolo Bonzini @ 2016-11-09 15:54 UTC (permalink / raw)
To: Michael S. Tsirkin
Cc: Felipe Franciosi, Stefan Hajnoczi, qemu-devel@nongnu.org
In-Reply-To: <20161109174645-mutt-send-email-mst@kernel.org>
On 09/11/2016 16:47, Michael S. Tsirkin wrote:
> On Wed, Nov 09, 2016 at 02:22:42PM +0100, Paolo Bonzini wrote:
>>
>>
>> On 09/11/2016 14:18, Felipe Franciosi wrote:
>>> Recent changes on vhost_dev_enable/disable_notifiers() produced a
>>> VirtioBusState vbus variable which can be used instead of the
>>> VIRTIO_BUS() macro. This commit just makes the code a little bit cleaner
>>> and more consistent.
>>>
>>> Signed-off-by: Felipe Franciosi <felipe@nutanix.com>
>>
>> Michael, what do you think? Perhaps it's simplest to just squash the
>> two patches (v2 of "vhost: Update 'ioeventfd_started' with host
>> notifiers" and this one).
>>
>> Paolo
>
> I think I'll apply both but why bother squashing?
Just because
VIRTIO_BUS(qbus)->ioeventfd_started = true;
from the first patch is ugly. :)
Paolo
>>> ---
>>> hw/virtio/vhost.c | 14 ++++++--------
>>> 1 file changed, 6 insertions(+), 8 deletions(-)
>>>
>>> diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
>>> index 1290963..7d29dad 100644
>>> --- a/hw/virtio/vhost.c
>>> +++ b/hw/virtio/vhost.c
>>> @@ -1198,20 +1198,18 @@ int vhost_dev_enable_notifiers(struct vhost_dev *hdev, VirtIODevice *vdev)
>>>
>>> virtio_device_stop_ioeventfd(vdev);
>>> for (i = 0; i < hdev->nvqs; ++i) {
>>> - r = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i,
>>> - true);
>>> + r = virtio_bus_set_host_notifier(vbus, hdev->vq_index + i, true);
>>> if (r < 0) {
>>> error_report("vhost VQ %d notifier binding failed: %d", i, -r);
>>> goto fail_vq;
>>> }
>>> }
>>> - VIRTIO_BUS(qbus)->ioeventfd_started = true;
>>> + vbus->ioeventfd_started = true;
>>>
>>> return 0;
>>> fail_vq:
>>> while (--i >= 0) {
>>> - e = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i,
>>> - false);
>>> + e = virtio_bus_set_host_notifier(vbus, hdev->vq_index + i, false);
>>> if (e < 0) {
>>> error_report("vhost VQ %d notifier cleanup error: %d", i, -r);
>>> }
>>> @@ -1230,17 +1228,17 @@ fail:
>>> void vhost_dev_disable_notifiers(struct vhost_dev *hdev, VirtIODevice *vdev)
>>> {
>>> BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
>>> + VirtioBusState *vbus = VIRTIO_BUS(qbus);
>>> int i, r;
>>>
>>> for (i = 0; i < hdev->nvqs; ++i) {
>>> - r = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i,
>>> - false);
>>> + r = virtio_bus_set_host_notifier(vbus, hdev->vq_index + i, false);
>>> if (r < 0) {
>>> error_report("vhost VQ %d notifier cleanup failed: %d", i, -r);
>>> }
>>> assert (r >= 0);
>>> }
>>> - VIRTIO_BUS(qbus)->ioeventfd_started = false;
>>> + vbus->ioeventfd_started = false;
>>> virtio_device_start_ioeventfd(vdev);
>>> }
>>>
>>>
^ permalink raw reply
* [PATCH 4/5] doc_rst: media: New SDR formats SC16, SC18 & SC20
From: Ramesh Shanmugasundaram @ 2016-11-09 15:44 UTC (permalink / raw)
To: robh+dt, mark.rutland, mchehab, hverkuil, sakari.ailus, crope
Cc: chris.paterson2, laurent.pinchart, geert+renesas, linux-media,
devicetree, linux-renesas-soc, Ramesh Shanmugasundaram
In-Reply-To: <1478706284-59134-1-git-send-email-ramesh.shanmugasundaram@bp.renesas.com>
This patch adds documentation for the three new SDR formats
V4L2_SDR_FMT_SCU16BE
V4L2_SDR_FMT_SCU18BE
V4L2_SDR_FMT_SCU20BE
Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
---
.../media/uapi/v4l/pixfmt-sdr-scu16be.rst | 80 ++++++++++++++++++++++
.../media/uapi/v4l/pixfmt-sdr-scu18be.rst | 80 ++++++++++++++++++++++
.../media/uapi/v4l/pixfmt-sdr-scu20be.rst | 80 ++++++++++++++++++++++
Documentation/media/uapi/v4l/sdr-formats.rst | 3 +
4 files changed, 243 insertions(+)
create mode 100644 Documentation/media/uapi/v4l/pixfmt-sdr-scu16be.rst
create mode 100644 Documentation/media/uapi/v4l/pixfmt-sdr-scu18be.rst
create mode 100644 Documentation/media/uapi/v4l/pixfmt-sdr-scu20be.rst
diff --git a/Documentation/media/uapi/v4l/pixfmt-sdr-scu16be.rst b/Documentation/media/uapi/v4l/pixfmt-sdr-scu16be.rst
new file mode 100644
index 0000000..7525378
--- /dev/null
+++ b/Documentation/media/uapi/v4l/pixfmt-sdr-scu16be.rst
@@ -0,0 +1,80 @@
+.. -*- coding: utf-8; mode: rst -*-
+
+.. _V4L2-SDR-FMT-SCU16BE:
+
+******************************
+V4L2_SDR_FMT_SCU16BE ('SC16')
+******************************
+
+Sliced complex unsigned 16-bit big endian IQ sample
+
+Description
+===========
+
+This format contains a sequence of complex number samples. Each complex
+number consist of two parts called In-phase and Quadrature (IQ). Both I
+and Q are represented as a 16 bit unsigned big endian number stored in
+32 bit space. The remaining unused bits within the 32 bit space will be
+padded with 0. I value starts first and Q value starts at an offset
+equalling half of the buffer size (i.e.) offset = buffersize/2. Out of
+the 16 bits, bit 15:2 (14 bit) is data and bit 1:0 (2 bit) can be any
+value.
+
+**Byte Order.**
+Each cell is one byte.
+
+.. flat-table::
+ :header-rows: 1
+ :stub-columns: 0
+
+ * - Offset:
+
+ - Byte B0
+
+ - Byte B1
+
+ - Byte B2
+
+ - Byte B3
+
+ * - start + 0:
+
+ - I'\ :sub:`0[13:6]`
+
+ - I'\ :sub:`0[5:0]; B1[1:0]=pad`
+
+ - pad
+
+ - pad
+
+ * - start + 4:
+
+ - I'\ :sub:`1[13:6]`
+
+ - I'\ :sub:`1[5:0]; B1[1:0]=pad`
+
+ - pad
+
+ - pad
+
+ * - ...
+
+ * - start + offset:
+
+ - Q'\ :sub:`0[13:6]`
+
+ - Q'\ :sub:`0[5:0]; B1[1:0]=pad`
+
+ - pad
+
+ - pad
+
+ * - start + offset + 4:
+
+ - Q'\ :sub:`1[13:6]`
+
+ - Q'\ :sub:`1[5:0]; B1[1:0]=pad`
+
+ - pad
+
+ - pad
diff --git a/Documentation/media/uapi/v4l/pixfmt-sdr-scu18be.rst b/Documentation/media/uapi/v4l/pixfmt-sdr-scu18be.rst
new file mode 100644
index 0000000..0ce714d
--- /dev/null
+++ b/Documentation/media/uapi/v4l/pixfmt-sdr-scu18be.rst
@@ -0,0 +1,80 @@
+.. -*- coding: utf-8; mode: rst -*-
+
+.. _V4L2-SDR-FMT-SCU18BE:
+
+******************************
+V4L2_SDR_FMT_SCU18BE ('SC18')
+******************************
+
+Sliced complex unsigned 18-bit big endian IQ sample
+
+Description
+===========
+
+This format contains a sequence of complex number samples. Each complex
+number consist of two parts called In-phase and Quadrature (IQ). Both I
+and Q are represented as a 18 bit unsigned big endian number stored in
+32 bit space. The remaining unused bits within the 32 bit space will be
+padded with 0. I value starts first and Q value starts at an offset
+equalling half of the buffer size (i.e.) offset = buffersize/2. Out of
+the 18 bits, bit 17:2 (16 bit) is data and bit 1:0 (2 bit) can be any
+value.
+
+**Byte Order.**
+Each cell is one byte.
+
+.. flat-table::
+ :header-rows: 1
+ :stub-columns: 0
+
+ * - Offset:
+
+ - Byte B0
+
+ - Byte B1
+
+ - Byte B2
+
+ - Byte B3
+
+ * - start + 0:
+
+ - I'\ :sub:`0[17:10]`
+
+ - I'\ :sub:`0[9:2]`
+
+ - I'\ :sub:`0[1:0]; B2[5:0]=pad`
+
+ - pad
+
+ * - start + 4:
+
+ - I'\ :sub:`1[17:10]`
+
+ - I'\ :sub:`1[9:2]`
+
+ - I'\ :sub:`1[1:0]; B2[5:0]=pad`
+
+ - pad
+
+ * - ...
+
+ * - start + offset:
+
+ - Q'\ :sub:`0[17:10]`
+
+ - Q'\ :sub:`0[9:2]`
+
+ - Q'\ :sub:`0[1:0]; B2[5:0]=pad`
+
+ - pad
+
+ * - start + offset + 4:
+
+ - Q'\ :sub:`1[17:10]`
+
+ - Q'\ :sub:`1[9:2]`
+
+ - Q'\ :sub:`1[1:0]; B2[5:0]=pad`
+
+ - pad
diff --git a/Documentation/media/uapi/v4l/pixfmt-sdr-scu20be.rst b/Documentation/media/uapi/v4l/pixfmt-sdr-scu20be.rst
new file mode 100644
index 0000000..ff2fe51
--- /dev/null
+++ b/Documentation/media/uapi/v4l/pixfmt-sdr-scu20be.rst
@@ -0,0 +1,80 @@
+.. -*- coding: utf-8; mode: rst -*-
+
+.. _V4L2-SDR-FMT-SCU20BE:
+
+******************************
+V4L2_SDR_FMT_SCU20BE ('SC20')
+******************************
+
+Sliced complex unsigned 20-bit big endian IQ sample
+
+Description
+===========
+
+This format contains a sequence of complex number samples. Each complex
+number consist of two parts called In-phase and Quadrature (IQ). Both I
+and Q are represented as a 20 bit unsigned big endian number stored in
+32 bit space. The remaining unused bits within the 32 bit space will be
+padded with 0. I value starts first and Q value starts at an offset
+equalling half of the buffer size (i.e.) offset = buffersize/2. Out of
+the 20 bits, bit 19:2 (18 bit) is data and bit 1:0 (2 bit) can be any
+value.
+
+**Byte Order.**
+Each cell is one byte.
+
+.. flat-table::
+ :header-rows: 1
+ :stub-columns: 0
+
+ * - Offset:
+
+ - Byte B0
+
+ - Byte B1
+
+ - Byte B2
+
+ - Byte B3
+
+ * - start + 0:
+
+ - I'\ :sub:`0[19:12]`
+
+ - I'\ :sub:`0[11:4]`
+
+ - I'\ :sub:`0[3:0]; B2[3:0]=pad`
+
+ - pad
+
+ * - start + 4:
+
+ - I'\ :sub:`1[19:12]`
+
+ - I'\ :sub:`1[11:4]`
+
+ - I'\ :sub:`1[3:0]; B2[3:0]=pad`
+
+ - pad
+
+ * - ...
+
+ * - start + offset:
+
+ - Q'\ :sub:`0[19:12]`
+
+ - Q'\ :sub:`0[11:4]`
+
+ - Q'\ :sub:`0[3:0]; B2[3:0]=pad`
+
+ - pad
+
+ * - start + offset + 4:
+
+ - Q'\ :sub:`1[19:12]`
+
+ - Q'\ :sub:`1[11:4]`
+
+ - Q'\ :sub:`1[3:0]; B2[3:0]=pad`
+
+ - pad
diff --git a/Documentation/media/uapi/v4l/sdr-formats.rst b/Documentation/media/uapi/v4l/sdr-formats.rst
index f863c08..4c01cf9 100644
--- a/Documentation/media/uapi/v4l/sdr-formats.rst
+++ b/Documentation/media/uapi/v4l/sdr-formats.rst
@@ -17,3 +17,6 @@ These formats are used for :ref:`SDR <sdr>` interface only.
pixfmt-sdr-cs08
pixfmt-sdr-cs14le
pixfmt-sdr-ru12le
+ pixfmt-sdr-scu16be
+ pixfmt-sdr-scu18be
+ pixfmt-sdr-scu20be
--
1.9.1
^ permalink raw reply related
* [PATCH 5/5] media: platform: rcar_drif: Add DRIF support
From: Ramesh Shanmugasundaram @ 2016-11-09 15:44 UTC (permalink / raw)
To: robh+dt, mark.rutland, mchehab, hverkuil, sakari.ailus, crope
Cc: chris.paterson2, laurent.pinchart, geert+renesas, linux-media,
devicetree, linux-renesas-soc, Ramesh Shanmugasundaram
In-Reply-To: <1478706284-59134-1-git-send-email-ramesh.shanmugasundaram@bp.renesas.com>
This patch adds Digital Radio Interface (DRIF) support to R-Car Gen3 SoCs.
The driver exposes each instance of DRIF as a V4L2 SDR device. A DRIF
device represents a channel and each channel can have one or two
sub-channels respectively depending on the target board.
DRIF supports only Rx functionality. It receives samples from a RF
frontend tuner chip it is interfaced with. The combination of DRIF and the
tuner device, which is registered as a sub-device, determines the receive
sample rate and format.
In order to be compliant as a V4L2 SDR device, DRIF needs to bind with
the tuner device, which can be provided by a third party vendor. DRIF acts
as a slave device and the tuner device acts as a master transmitting the
samples. The driver allows asynchronous binding of a tuner device that
is registered as a v4l2 sub-device. The driver can learn about the tuner
it is interfaced with based on port endpoint properties of the device in
device tree. The V4L2 SDR device inherits the controls exposed by the
tuner device.
The device can also be configured to use either one or both of the data
pins at runtime based on the master (tuner) configuration.
Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
---
.../devicetree/bindings/media/renesas,drif.txt | 136 ++
drivers/media/platform/Kconfig | 25 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/rcar_drif.c | 1574 ++++++++++++++++++++
4 files changed, 1736 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/renesas,drif.txt
create mode 100644 drivers/media/platform/rcar_drif.c
diff --git a/Documentation/devicetree/bindings/media/renesas,drif.txt b/Documentation/devicetree/bindings/media/renesas,drif.txt
new file mode 100644
index 0000000..d65368a
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/renesas,drif.txt
@@ -0,0 +1,136 @@
+Renesas R-Car Gen3 Digital Radio Interface controller (DRIF)
+------------------------------------------------------------
+
+R-Car Gen3 DRIF is a serial slave device. It interfaces with a master
+device as shown below
+
++---------------------+ +---------------------+
+| |-----SCK------->|CLK |
+| Master |-----SS-------->|SYNC DRIFn (slave) |
+| |-----SD0------->|D0 |
+| |-----SD1------->|D1 |
++---------------------+ +---------------------+
+
+Each DRIF channel (drifn) consists of two sub-channels (drifn0 & drifn1).
+The sub-channels are like two individual channels in itself that share the
+common CLK & SYNC. Each sub-channel has it's own dedicated resources like
+irq, dma channels, address space & clock.
+
+The device tree model represents the channel and each of it's sub-channel
+as a separate node. The parent channel ties the sub-channels together with
+their phandles.
+
+Required properties of a sub-channel:
+-------------------------------------
+- compatible: "renesas,r8a7795-drif" if DRIF controller is a part of R8A7795 SoC.
+ "renesas,rcar-gen3-drif" for a generic R-Car Gen3 compatible device.
+ When compatible with the generic version, nodes must list the
+ SoC-specific version corresponding to the platform first
+ followed by the generic version.
+- reg: offset and length of that sub-channel.
+- interrupts: associated with that sub-channel.
+- clocks: phandle and clock specifier of that sub-channel.
+- clock-names: clock input name string: "fck".
+- dmas: phandles to the DMA channel of that sub-channel.
+- dma-names: names of the DMA channel: "rx".
+
+Optional properties of a sub-channel:
+-------------------------------------
+- power-domains: phandle to the respective power domain.
+
+Required properties of a channel:
+---------------------------------
+- pinctrl-0: pin control group to be used for this channel.
+- pinctrl-names: must be "default".
+- sub-channels : phandles to the two sub-channels.
+
+Optional properties of a channel:
+---------------------------------
+- port: child port node of a channel that defines the local and remote
+ endpoints. The remote endpoint is assumed to be a tuner subdevice
+ endpoint.
+- renesas,syncmd : sync mode
+ 0 (Frame start sync pulse mode. 1-bit width pulse
+ indicates start of a frame)
+ 1 (L/R sync or I2S mode) (default)
+- renesas,lsb-first : empty property indicates lsb bit is received first.
+ When not defined msb bit is received first (default)
+- renesas,syncac-pol-high : empty property indicates sync signal polarity.
+ When defined, active high or high->low sync signal.
+ When not defined, active low or low->high sync signal
+ (default)
+- renesas,dtdl : delay between sync signal and start of reception.
+ Must contain one of the following values:
+ 0 (no bit delay)
+ 50 (0.5-clock-cycle delay)
+ 100 (1-clock-cycle delay) (default)
+ 150 (1.5-clock-cycle delay)
+ 200 (2-clock-cycle delay)
+- renesas,syncdl : delay between end of reception and sync signal edge.
+ Must contain one of the following values:
+ 0 (no bit delay) (default)
+ 50 (0.5-clock-cycle delay)
+ 100 (1-clock-cycle delay)
+ 150 (1.5-clock-cycle delay)
+ 200 (2-clock-cycle delay)
+ 300 (3-clock-cycle delay)
+
+Example
+--------
+
+SoC common dtsi file
+
+ drif00: rif@e6f40000 {
+ compatible = "renesas,r8a7795-drif",
+ "renesas,rcar-gen3-drif";
+ reg = <0 0xe6f40000 0 0x64>;
+ interrupts = <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 515>;
+ clock-names = "fck";
+ dmas = <&dmac1 0x20>, <&dmac2 0x20>;
+ dma-names = "rx", "rx";
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+ };
+
+ drif01: rif@e6f50000 {
+ compatible = "renesas,r8a7795-drif",
+ "renesas,rcar-gen3-drif";
+ reg = <0 0xe6f50000 0 0x64>;
+ interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 514>;
+ clock-names = "fck";
+ dmas = <&dmac1 0x22>, <&dmac2 0x22>;
+ dma-names = "rx", "rx";
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+ };
+
+ drif0: rif@0 {
+ compatible = "renesas,r8a7795-drif",
+ "renesas,rcar-gen3-drif";
+ sub-channels = <&drif00>, <&drif01>;
+ status = "disabled";
+ };
+
+Board specific dts file
+
+&drif00 {
+ status = "okay";
+};
+
+&drif01 {
+ status = "okay";
+};
+
+&drif0 {
+ pinctrl-0 = <&drif0_pins>;
+ pinctrl-names = "default";
+ renesas,syncac-pol-high;
+ status = "okay";
+ port {
+ drif0_ep: endpoint {
+ remote-endpoint = <&tuner_subdev_ep>;
+ };
+ };
+};
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 754edbf1..0ae83a8 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -393,3 +393,28 @@ menuconfig DVB_PLATFORM_DRIVERS
if DVB_PLATFORM_DRIVERS
source "drivers/media/platform/sti/c8sectpfe/Kconfig"
endif #DVB_PLATFORM_DRIVERS
+
+menuconfig SDR_PLATFORM_DRIVERS
+ bool "SDR platform devices"
+ depends on MEDIA_SDR_SUPPORT
+ default n
+ ---help---
+ Say Y here to enable support for platform-specific SDR Drivers.
+
+if SDR_PLATFORM_DRIVERS
+
+config VIDEO_RCAR_DRIF
+ tristate "Renesas Digitial Radio Interface (DRIF)"
+ depends on VIDEO_V4L2 && HAS_DMA
+ depends on ARCH_RENESAS
+ select VIDEOBUF2_VMALLOC
+ ---help---
+ Say Y if you want to enable R-Car Gen3 DRIF support. DRIF is Digital
+ Radio Interface that interfaces with an RF front end chip. It is a
+ receiver of digital data which uses DMA to transfer received data to
+ a configured location for an application to use.
+
+ To compile this driver as a module, choose M here; the module
+ will be called rcar_drif.
+
+endif # SDR_PLATFORM_DRIVERS
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index f842933..49ce238 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SOC_CAMERA) += soc_camera/
obj-$(CONFIG_VIDEO_RENESAS_FCP) += rcar-fcp.o
obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o
+obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o
obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/
obj-y += omap/
diff --git a/drivers/media/platform/rcar_drif.c b/drivers/media/platform/rcar_drif.c
new file mode 100644
index 0000000..34dc282
--- /dev/null
+++ b/drivers/media/platform/rcar_drif.c
@@ -0,0 +1,1574 @@
+/*
+ * R-Car Gen3 Digital Radio Interface (DRIF) driver
+ *
+ * Copyright (C) 2016 Renesas Electronics Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * The R-Car DRIF is a receive only MSIOF like controller with an
+ * external master device driving the SCK. It receives data into a FIFO,
+ * then this driver uses the SYS-DMAC engine to move the data from
+ * the device to memory.
+ *
+ * Each DRIF channel supports two sub-channels with their own resources
+ * like module clk, register set and dma.
+ *
+ * Depending on the master device, a DRIF channel can use
+ * (1) both sub-channels (D0 & D1 pins) to receive data in parallel (or)
+ * (2) one sub-channel (D0 or D1) to receive data
+ *
+ * The primary design goal of this controller is to act as Digitial Radio
+ * Interface that receives digital samples from a tuner device. Hence the
+ * driver exposes the device as a V4L2 SDR device. In order to qualify as
+ * a V4L2 SDR device, it should possess tuner interface as mandated by the
+ * framework. This driver expects a tuner driver (sub-device) to bind
+ * asynchronously with this device and the combined drivers shall expose
+ * a V4L2 compliant SDR device. The DRIF driver is independent of the
+ * tuner vendor.
+ */
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/ioctl.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+/* DRIF register offsets */
+#define RCAR_DRIF_SITMDR1 0x00
+#define RCAR_DRIF_SITMDR2 0x04
+#define RCAR_DRIF_SITMDR3 0x08
+#define RCAR_DRIF_SIRMDR1 0x10
+#define RCAR_DRIF_SIRMDR2 0x14
+#define RCAR_DRIF_SIRMDR3 0x18
+#define RCAR_DRIF_SICTR 0x28
+#define RCAR_DRIF_SIFCTR 0x30
+#define RCAR_DRIF_SISTR 0x40
+#define RCAR_DRIF_SIIER 0x44
+#define RCAR_DRIF_SIRFDR 0x60
+
+#define RCAR_DRIF_RFOVF BIT(3) /* Receive FIFO overflow */
+#define RCAR_DRIF_RFUDF BIT(4) /* Receive FIFO underflow */
+#define RCAR_DRIF_RFSERR BIT(5) /* Receive frame sync error */
+#define RCAR_DRIF_REOF BIT(7) /* Frame reception end */
+#define RCAR_DRIF_RDREQ BIT(12) /* Receive data xfer req */
+#define RCAR_DRIF_RFFUL BIT(13) /* Receive FIFO full */
+
+/* SIRMDR1 */
+#define RCAR_DRIF_SIRMDR1_SYNCMD_FRAME (0 << 28)
+#define RCAR_DRIF_SIRMDR1_SYNCMD_LR (3 << 28)
+
+#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH (0 << 25)
+#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW (1 << 25)
+
+#define RCAR_DRIF_SIRMDR1_MSB_FIRST (0 << 24)
+#define RCAR_DRIF_SIRMDR1_LSB_FIRST (1 << 24)
+
+#define RCAR_DRIF_SIRMDR1_DTDL_0 (0 << 20)
+#define RCAR_DRIF_SIRMDR1_DTDL_1 (1 << 20)
+#define RCAR_DRIF_SIRMDR1_DTDL_2 (2 << 20)
+#define RCAR_DRIF_SIRMDR1_DTDL_0PT5 (5 << 20)
+#define RCAR_DRIF_SIRMDR1_DTDL_1PT5 (6 << 20)
+
+#define RCAR_DRIF_SIRMDR1_SYNCDL_0 (0 << 20)
+#define RCAR_DRIF_SIRMDR1_SYNCDL_1 (1 << 20)
+#define RCAR_DRIF_SIRMDR1_SYNCDL_2 (2 << 20)
+#define RCAR_DRIF_SIRMDR1_SYNCDL_3 (3 << 20)
+#define RCAR_DRIF_SIRMDR1_SYNCDL_0PT5 (5 << 20)
+#define RCAR_DRIF_SIRMDR1_SYNCDL_1PT5 (6 << 20)
+
+#define RCAR_DRIF_MDR_GRPCNT(n) (((n) - 1) << 30)
+#define RCAR_DRIF_MDR_BITLEN(n) (((n) - 1) << 24)
+#define RCAR_DRIF_MDR_WDCNT(n) (((n) - 1) << 16)
+
+/* Hidden Transmit register that controls CLK & SYNC */
+#define RCAR_DRIF_SITMDR1_PCON BIT(30)
+
+#define RCAR_DRIF_SICTR_RX_RISING_EDGE BIT(26)
+#define RCAR_DRIF_SICTR_RX_EN BIT(8)
+#define RCAR_DRIF_SICTR_RESET BIT(0)
+
+/* Constants */
+#define RCAR_DRIF_MAX_NUM_HWBUFS 32
+#define RCAR_DRIF_MAX_DEVS 4
+#define RCAR_DRIF_DEFAULT_NUM_HWBUFS 16
+#define RCAR_DRIF_DEFAULT_HWBUF_SIZE (4 * PAGE_SIZE)
+#define RCAR_DRIF_MAX_SUBCHANS 2
+#define RCAR_SDR_BUFFER_SIZE SZ_64K
+
+/* Internal buffer status flags */
+#define RCAR_DRIF_BUF_DONE BIT(0) /* DMA completed */
+#define RCAR_DRIF_BUF_OVERFLOW BIT(1) /* Overflow detected */
+
+#define to_rcar_drif_buf_pair(p, ch, idx) (p->sch[!(ch)]->buf[idx])
+
+#define for_each_rcar_drif_subchannel(sch, schans_mask) \
+ for_each_set_bit(sch, schans_mask, RCAR_DRIF_MAX_SUBCHANS)
+
+#define for_each_rcar_drif_subdev(sd, tmp, ch) \
+ list_for_each_entry_safe(sd, tmp, &ch->v4l2_dev.subdevs, list)
+
+/* Debug */
+static unsigned int debug;
+module_param(debug, uint, 0644);
+MODULE_PARM_DESC(debug, "activate debug info");
+
+#define rdrif_dbg(level, ch, fmt, arg...) \
+ v4l2_dbg(level, debug, &ch->v4l2_dev, fmt, ## arg)
+
+#define rdrif_err(ch, fmt, arg...) \
+ v4l2_err(&ch->v4l2_dev, fmt, ## arg)
+
+/* Stream formats */
+struct rcar_drif_format {
+ char *name;
+ u32 pixelformat;
+ u32 buffersize;
+ u32 wdlen;
+ u32 num_schans;
+};
+
+/* Format descriptions for capture and preview */
+static const struct rcar_drif_format formats[] = {
+ {
+ .name = "Sliced Complex U16BE",
+ .pixelformat = V4L2_SDR_FMT_SCU16BE,
+ .buffersize = RCAR_SDR_BUFFER_SIZE,
+ .wdlen = 16,
+ .num_schans = 2,
+ },
+ {
+ .name = "Sliced Complex U18BE",
+ .pixelformat = V4L2_SDR_FMT_SCU18BE,
+ .buffersize = RCAR_SDR_BUFFER_SIZE,
+ .wdlen = 18,
+ .num_schans = 2,
+ },
+ {
+ .name = "Sliced Complex U20BE",
+ .pixelformat = V4L2_SDR_FMT_SCU20BE,
+ .buffersize = RCAR_SDR_BUFFER_SIZE,
+ .wdlen = 20,
+ .num_schans = 2,
+ },
+};
+
+static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats);
+
+/* Buffer for a received frame from one or both sub-channels */
+struct rcar_drif_frame_buf {
+ /* Common v4l buffer stuff -- must be first */
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+};
+
+struct rcar_drif_async_subdev {
+ struct v4l2_subdev *sd;
+ struct v4l2_async_subdev asd;
+};
+
+/* DMA buffer */
+struct rcar_drif_hwbuf {
+ void *addr; /* Cpu-side address */
+ unsigned int status; /* Buffer status flags */
+};
+
+/* Sub-channel */
+struct rcar_drif_subchan {
+ struct rcar_drif_chan *parent; /* Parent channel */
+ struct platform_device *pdev; /* Sub-channel's pdev */
+ void __iomem *base; /* Base register address */
+ resource_size_t start; /* I/O resource offset */
+ struct dma_chan *dmach; /* Reserved DMA channel */
+ struct clk *clkp; /* Module clock */
+ struct rcar_drif_hwbuf *buf[RCAR_DRIF_MAX_NUM_HWBUFS];
+ dma_addr_t dma_handle; /* Handle for all bufs */
+ unsigned int num; /* Sub-channel number */
+};
+
+/* Channel */
+struct rcar_drif_chan {
+ struct device *dev; /* Platform device */
+ struct video_device vdev; /* SDR device */
+ struct v4l2_device v4l2_dev; /* V4l2 device */
+
+ /* Videobuf2 queue and queued buffers list */
+ struct vb2_queue vb_queue;
+ struct list_head queued_bufs;
+ spinlock_t queued_bufs_lock; /* Protects queued_bufs */
+
+ struct mutex v4l2_mutex; /* To serialize ioctls */
+ struct mutex vb_queue_mutex; /* To serialize streaming ioctls */
+ struct v4l2_ctrl_handler ctrl_hdl; /* SDR control handler */
+ struct v4l2_async_notifier notifier; /* For subdev (tuner) */
+
+ /* Current V4L2 SDR format array index */
+ unsigned int fmt_idx;
+
+ /* Device tree SYNC properties */
+ u32 mdr1;
+
+ /* Internals */
+ struct rcar_drif_subchan *sch[2]; /* DRIFx0 and DRIFx1 */
+ unsigned long hw_schans_mask; /* Enabled sub-channels per DT */
+ unsigned long cur_schans_mask; /* Used sub-channels for an SDR FMT */
+ u32 num_hw_schans; /* Num of DT enabled sub-channels */
+ u32 num_cur_schans; /* Num of used sub-channels */
+ u32 num_hwbufs; /* Num of DMA buffers */
+ u32 hwbuf_size; /* Each DMA buffer size */
+ u32 produced; /* Buffers produced by dev */
+};
+
+/* Allocate buffer context */
+static int rcar_drif_alloc_bufctxt(struct rcar_drif_chan *ch)
+{
+ struct rcar_drif_hwbuf *bufctx;
+ unsigned int i, idx;
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ bufctx = kcalloc(ch->num_hwbufs, sizeof(*bufctx), GFP_KERNEL);
+ if (!bufctx)
+ return -ENOMEM;
+
+ for (idx = 0; idx < ch->num_hwbufs; idx++)
+ ch->sch[i]->buf[idx] = bufctx + idx;
+ }
+ return 0;
+}
+
+/* Release buffer context */
+static void rcar_drif_release_bufctxt(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ kfree(ch->sch[i]->buf[0]);
+ ch->sch[i]->buf[0] = NULL;
+ }
+}
+
+/* Release DMA channel */
+static void rcar_drif_release_dmachannel(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask)
+ if (ch->sch[i]->dmach) {
+ dma_release_channel(ch->sch[i]->dmach);
+ ch->sch[i]->dmach = NULL;
+ }
+}
+
+/* Allocate DMA channel */
+static int rcar_drif_alloc_dmachannel(struct rcar_drif_chan *ch)
+{
+ struct dma_slave_config dma_cfg;
+ unsigned int i;
+ int ret = -ENODEV;
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ struct rcar_drif_subchan *sch = ch->sch[i];
+
+ sch->dmach = dma_request_slave_channel(&sch->pdev->dev, "rx");
+ if (!sch->dmach) {
+ rdrif_err(ch, "schan%u: dma channel req failed\n", i);
+ goto dmach_error;
+ }
+
+ /* Configure slave */
+ memset(&dma_cfg, 0, sizeof(dma_cfg));
+ dma_cfg.src_addr = (phys_addr_t)(sch->start + RCAR_DRIF_SIRFDR);
+ dma_cfg.dst_addr = 0;
+ dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ret = dmaengine_slave_config(sch->dmach, &dma_cfg);
+ if (ret) {
+ rdrif_err(ch, "schan%u: dma slave config failed\n", i);
+ goto dmach_error;
+ }
+ }
+ return 0;
+
+dmach_error:
+ rcar_drif_release_dmachannel(ch);
+ return ret;
+}
+
+/* Release queued vb2 buffers */
+static void rcar_drif_release_queued_bufs(struct rcar_drif_chan *ch,
+ enum vb2_buffer_state state)
+{
+ struct rcar_drif_frame_buf *fbuf, *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ch->queued_bufs_lock, flags);
+ list_for_each_entry_safe(fbuf, tmp, &ch->queued_bufs, list) {
+ list_del(&fbuf->list);
+ vb2_buffer_done(&fbuf->vb.vb2_buf, state);
+ }
+ spin_unlock_irqrestore(&ch->queued_bufs_lock, flags);
+}
+
+/* Set MDR defaults */
+static inline void rcar_drif_set_mdr1(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+
+ /* Set defaults for both sub-channels */
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ /* Refer MSIOF section in manual for this register setting */
+ writel(RCAR_DRIF_SITMDR1_PCON,
+ ch->sch[i]->base + RCAR_DRIF_SITMDR1);
+
+ /* Setup MDR1 value */
+ writel(ch->mdr1, ch->sch[i]->base + RCAR_DRIF_SIRMDR1);
+
+ rdrif_dbg(2, ch, "schan%u: mdr1 = 0x%08x",
+ i, readl(ch->sch[i]->base + RCAR_DRIF_SIRMDR1));
+ }
+}
+
+/* Extract bitlen and wdcnt from given word length */
+static int rcar_drif_convert_wdlen(struct rcar_drif_chan *ch,
+ u32 wdlen, u32 *bitlen, u32 *wdcnt)
+{
+ unsigned int i, nr_wds;
+
+ /* FIFO register size is 32 bits */
+ for (i = 0; i < 32; i++) {
+ nr_wds = wdlen % (32 - i);
+ if (nr_wds == 0) {
+ *bitlen = (32 - i);
+ *wdcnt = (wdlen / *bitlen);
+ break;
+ }
+ }
+
+ /* Sanity check range */
+ if ((i == 32) || !((*bitlen >= 8) && (*bitlen <= 32)) ||
+ !((*wdcnt >= 1) && (*wdcnt <= 64))) {
+ rdrif_err(ch, "invalid wdlen %u configured\n", wdlen);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Set DRIF receive format */
+static int rcar_drif_set_format(struct rcar_drif_chan *ch)
+{
+ u32 bitlen, wdcnt, wdlen;
+ unsigned int i;
+ int ret = -EINVAL;
+
+ wdlen = formats[ch->fmt_idx].wdlen;
+ rdrif_dbg(2, ch, "setfmt: idx %u, wdlen %u, num_schans %u\n",
+ ch->fmt_idx, wdlen, formats[ch->fmt_idx].num_schans);
+
+ /* Sanity check */
+ if (formats[ch->fmt_idx].num_schans > ch->num_cur_schans) {
+ rdrif_err(ch, "fmt idx %u current schans %u mismatch\n",
+ ch->fmt_idx, ch->num_cur_schans);
+ return ret;
+ }
+
+ /* Get bitlen & wdcnt from wdlen */
+ ret = rcar_drif_convert_wdlen(ch, wdlen, &bitlen, &wdcnt);
+ if (ret)
+ return ret;
+
+ /* Setup group, bitlen & wdcnt */
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ u32 mdr;
+
+ /* Two groups */
+ mdr = RCAR_DRIF_MDR_GRPCNT(2) | RCAR_DRIF_MDR_BITLEN(bitlen) |
+ RCAR_DRIF_MDR_WDCNT(wdcnt);
+ writel(mdr, ch->sch[i]->base + RCAR_DRIF_SIRMDR2);
+
+ mdr = RCAR_DRIF_MDR_BITLEN(bitlen) | RCAR_DRIF_MDR_WDCNT(wdcnt);
+ writel(mdr, ch->sch[i]->base + RCAR_DRIF_SIRMDR3);
+
+ rdrif_dbg(2, ch, "schan%u: new mdr[2,3] = 0x%08x, 0x%08x\n",
+ i, readl(ch->sch[i]->base + RCAR_DRIF_SIRMDR2),
+ readl(ch->sch[i]->base + RCAR_DRIF_SIRMDR3));
+ }
+ return ret;
+}
+
+/* Release DMA buffers */
+static void rcar_drif_release_buf(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ struct rcar_drif_subchan *sch = ch->sch[i];
+
+ /* First entry of the sub-channel contains the dma buf ptr */
+ if (sch->buf[0] && sch->buf[0]->addr) {
+ dma_free_coherent(&sch->pdev->dev,
+ ch->hwbuf_size * ch->num_hwbufs,
+ sch->buf[0]->addr, sch->dma_handle);
+ sch->buf[0]->addr = NULL;
+ }
+ }
+}
+
+/* Request DMA buffers */
+static int rcar_drif_request_buf(struct rcar_drif_chan *ch)
+{
+ int ret = -ENOMEM;
+ unsigned int i, j;
+ void *addr;
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ struct rcar_drif_subchan *sch = ch->sch[i];
+
+ /* Allocate DMA buffers */
+ addr = dma_alloc_coherent(&sch->pdev->dev,
+ ch->hwbuf_size * ch->num_hwbufs,
+ &sch->dma_handle, GFP_KERNEL);
+ if (!addr) {
+ rdrif_err(ch,
+ "schan%u: dma alloc failed. num_hwbufs %u size %u\n",
+ i, ch->num_hwbufs, ch->hwbuf_size);
+ goto alloc_error;
+ }
+
+ /* Split the chunk and populate bufctxt */
+ for (j = 0; j < ch->num_hwbufs; j++) {
+ sch->buf[j]->addr = addr + (j * ch->hwbuf_size);
+ sch->buf[j]->status = 0;
+ }
+ }
+
+ return 0;
+
+alloc_error:
+ return ret;
+}
+
+/* Setup vb_queue minimum buffer requirements */
+static int rcar_drif_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct rcar_drif_chan *ch = vb2_get_drv_priv(vq);
+
+ /* Need at least 16 buffers */
+ if (vq->num_buffers + *num_buffers < 16)
+ *num_buffers = 16 - vq->num_buffers;
+
+ *num_planes = 1;
+ sizes[0] = PAGE_ALIGN(formats[ch->fmt_idx].buffersize);
+
+ rdrif_dbg(2, ch, "num_bufs %d sizes[0] %d\n", *num_buffers, sizes[0]);
+ return 0;
+}
+
+/* Enqueue buffer */
+static void rcar_drif_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct rcar_drif_chan *ch = vb2_get_drv_priv(vb->vb2_queue);
+ struct rcar_drif_frame_buf *fbuf =
+ container_of(vbuf, struct rcar_drif_frame_buf, vb);
+ unsigned long flags;
+
+ rdrif_dbg(2, ch, "buf_queue idx %u\n", vb->index);
+ spin_lock_irqsave(&ch->queued_bufs_lock, flags);
+ list_add_tail(&fbuf->list, &ch->queued_bufs);
+ spin_unlock_irqrestore(&ch->queued_bufs_lock, flags);
+}
+
+/* Deliver buffer to user space */
+static void rcar_drif_deliver_buf(struct rcar_drif_subchan *sch, u32 idx)
+{
+ struct rcar_drif_chan *ch = sch->parent;
+ struct rcar_drif_frame_buf *fbuf;
+ unsigned long flags;
+ bool overflow = false;
+
+ spin_lock_irqsave(&ch->queued_bufs_lock, flags);
+ fbuf = list_first_entry_or_null(&ch->queued_bufs, struct
+ rcar_drif_frame_buf, list);
+ if (!fbuf) {
+ /*
+ * App is late in enqueing buffers. Samples lost & there will
+ * be a gap in sequence number when app recovers
+ */
+ rdrif_dbg(1, ch, "\napp late: schan%u: prod %u\n",
+ sch->num, ch->produced);
+ ch->produced++; /* Increment the produced count anyway */
+ spin_unlock_irqrestore(&ch->queued_bufs_lock, flags);
+ return;
+ }
+ list_del(&fbuf->list);
+ spin_unlock_irqrestore(&ch->queued_bufs_lock, flags);
+
+ if (ch->num_cur_schans == RCAR_DRIF_MAX_SUBCHANS) {
+ struct rcar_drif_hwbuf *buf_i, *buf_q;
+
+ if (sch->num) {
+ buf_i = to_rcar_drif_buf_pair(ch, sch->num, idx);
+ buf_q = sch->buf[idx];
+ } else {
+ buf_i = sch->buf[idx];
+ buf_q = to_rcar_drif_buf_pair(ch, sch->num, idx);
+ }
+ memcpy(vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0),
+ buf_i->addr, ch->hwbuf_size);
+ memcpy(vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0) + ch->hwbuf_size,
+ buf_q->addr, ch->hwbuf_size);
+
+ if ((buf_i->status | buf_q->status) & RCAR_DRIF_BUF_OVERFLOW) {
+ overflow = true;
+ /* Clear the flag in status */
+ buf_i->status &= ~RCAR_DRIF_BUF_OVERFLOW;
+ buf_q->status &= ~RCAR_DRIF_BUF_OVERFLOW;
+ }
+ } else {
+ struct rcar_drif_hwbuf *buf_iq = sch->buf[idx];
+
+ memcpy(vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0),
+ buf_iq->addr, ch->hwbuf_size);
+
+ if (buf_iq->status & RCAR_DRIF_BUF_OVERFLOW) {
+ overflow = true;
+ /* Clear the flag in status */
+ buf_iq->status &= ~RCAR_DRIF_BUF_OVERFLOW;
+ }
+ }
+
+ rdrif_dbg(2, ch, "schan%u: prod %u\n", sch->num, ch->produced);
+
+ fbuf->vb.field = V4L2_FIELD_NONE;
+ fbuf->vb.sequence = ch->produced++;
+ fbuf->vb.vb2_buf.timestamp = ktime_get_ns();
+ vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0,
+ formats[ch->fmt_idx].buffersize);
+
+ /* Set error state on overflow */
+ if (overflow)
+ vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ else
+ vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+static inline bool rcar_drif_buf_pairs_done(struct rcar_drif_hwbuf *buf1,
+ struct rcar_drif_hwbuf *buf2)
+{
+ return (buf1->status & buf2->status & RCAR_DRIF_BUF_DONE);
+}
+
+/* Check if DMA of the paired sub-channel is also complete */
+static void rcar_drif_check_dmapair(struct rcar_drif_subchan *sch, u32 idx)
+{
+ struct rcar_drif_chan *ch = sch->parent;
+ struct rcar_drif_hwbuf *buf, *pair;
+
+ buf = sch->buf[idx];
+ pair = to_rcar_drif_buf_pair(ch, sch->num, idx);
+ buf->status |= RCAR_DRIF_BUF_DONE;
+
+ rdrif_dbg(2, ch, "schan%u: status: buf %u pair %u\n",
+ sch->num, buf->status, pair->status);
+
+ /* Check if both DMA buffers are done */
+ if (rcar_drif_buf_pairs_done(buf, pair)) {
+ /* Clear status flag */
+ buf->status &= ~RCAR_DRIF_BUF_DONE;
+ pair->status &= ~RCAR_DRIF_BUF_DONE;
+
+ /* Deliver buffer to user */
+ rcar_drif_deliver_buf(sch, idx);
+ }
+}
+
+/* DMA callback for each stage */
+static void rcar_drif_dma_complete(void *dma_async_param)
+{
+ struct rcar_drif_subchan *sch =
+ (struct rcar_drif_subchan *)dma_async_param;
+ struct rcar_drif_chan *ch = sch->parent;
+ struct rcar_drif_hwbuf *buf;
+ u32 str, idx;
+
+ mutex_lock(&ch->vb_queue_mutex);
+
+ /* DMA can be terminated while the callback was waiting on lock */
+ if (!vb2_is_streaming(&ch->vb_queue)) {
+ mutex_unlock(&ch->vb_queue_mutex);
+ return;
+ }
+
+ idx = ch->produced % ch->num_hwbufs;
+ buf = sch->buf[idx];
+
+ /* Check for DRIF errors */
+ str = readl(sch->base + RCAR_DRIF_SISTR);
+ if (unlikely(str & RCAR_DRIF_RFOVF)) {
+ /* Writing the same clears it */
+ writel(str, sch->base + RCAR_DRIF_SISTR);
+
+ /* Overflow: some samples are lost */
+ buf->status |= RCAR_DRIF_BUF_OVERFLOW;
+ rdrif_dbg(1, ch, "\nfifo overflow: schan%u: prod %d\n",
+ sch->num, ch->produced);
+ }
+
+ /* Check if the rx'ed data can be delivered to user */
+ if (ch->num_cur_schans == RCAR_DRIF_MAX_SUBCHANS)
+ rcar_drif_check_dmapair(sch, idx);
+ else
+ rcar_drif_deliver_buf(sch, idx);
+
+ mutex_unlock(&ch->vb_queue_mutex);
+}
+
+static int rcar_drif_qbuf(struct rcar_drif_subchan *sch)
+{
+ struct rcar_drif_chan *ch = sch->parent;
+ dma_addr_t addr = sch->dma_handle;
+ struct dma_async_tx_descriptor *rxd;
+ dma_cookie_t cookie;
+ int ret = -EIO;
+
+ /* Setup cyclic DMA with given buffers */
+ rxd = dmaengine_prep_dma_cyclic(sch->dmach, addr,
+ ch->hwbuf_size * ch->num_hwbufs,
+ ch->hwbuf_size, DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!rxd) {
+ rdrif_err(ch, "schan%u: prep dma cyclic failed\n", sch->num);
+ return ret;
+ }
+
+ /* Submit descriptor */
+ rxd->callback = rcar_drif_dma_complete;
+ rxd->callback_param = sch;
+ cookie = dmaengine_submit(rxd);
+ if (dma_submit_error(cookie)) {
+ rdrif_err(ch, "schan%u: dma submit failed\n", sch->num);
+ return ret;
+ }
+
+ dma_async_issue_pending(sch->dmach);
+ return 0;
+}
+
+/* Enable reception */
+static int rcar_drif_enable_rx(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+ u32 ctr;
+ int ret;
+
+ /*
+ * When both sub-channels are enabled, they can be syncronized only by
+ * the master
+ */
+
+ /* Enable receive */
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ ctr = readl(ch->sch[i]->base + RCAR_DRIF_SICTR);
+ ctr |= (RCAR_DRIF_SICTR_RX_RISING_EDGE |
+ RCAR_DRIF_SICTR_RX_EN);
+ writel(ctr, ch->sch[i]->base + RCAR_DRIF_SICTR);
+ }
+
+ /* Check receive enabled */
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ ret = readl_poll_timeout(ch->sch[i]->base + RCAR_DRIF_SICTR,
+ ctr, ctr & RCAR_DRIF_SICTR_RX_EN,
+ 2, 500000);
+ if (ret) {
+ rdrif_err(ch, "schan%u: rx en failed. ctr 0x%08x\n",
+ i, readl(ch->sch[i]->base + RCAR_DRIF_SICTR));
+ break;
+ }
+ }
+ return ret;
+}
+
+/* Disable reception */
+static void rcar_drif_disable_rx(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+ u32 ctr;
+ int ret;
+
+ /* Disable receive */
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ ctr = readl(ch->sch[i]->base + RCAR_DRIF_SICTR);
+ ctr &= ~RCAR_DRIF_SICTR_RX_EN;
+ writel(ctr, ch->sch[i]->base + RCAR_DRIF_SICTR);
+ }
+
+ /* Check receive disabled */
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ ret = readl_poll_timeout(ch->sch[i]->base + RCAR_DRIF_SICTR,
+ ctr, !(ctr & RCAR_DRIF_SICTR_RX_EN),
+ 2, 500000);
+ if (ret)
+ dev_warn(&ch->vdev.dev,
+ "schan%u: failed to disable rx. ctr 0x%08x\n",
+ i, readl(ch->sch[i]->base + RCAR_DRIF_SICTR));
+ }
+}
+
+/* Start sub-channel */
+static int rcar_drif_start_subchan(struct rcar_drif_subchan *sch)
+{
+ struct rcar_drif_chan *ch = sch->parent;
+ u32 ctr, str;
+ int ret;
+
+ /* Reset receive */
+ writel(RCAR_DRIF_SICTR_RESET, sch->base + RCAR_DRIF_SICTR);
+ ret = readl_poll_timeout(sch->base + RCAR_DRIF_SICTR,
+ ctr, !(ctr & RCAR_DRIF_SICTR_RESET),
+ 2, 500000);
+ if (ret) {
+ rdrif_err(ch, "schan%u: failed to reset rx. ctr 0x%08x\n",
+ sch->num, readl(sch->base + RCAR_DRIF_SICTR));
+ return ret;
+ }
+
+ /* Queue buffers for DMA */
+ ret = rcar_drif_qbuf(sch);
+ if (ret)
+ return ret;
+
+ /* Clear status register flags */
+ str = RCAR_DRIF_RFFUL | RCAR_DRIF_REOF | RCAR_DRIF_RFSERR |
+ RCAR_DRIF_RFUDF | RCAR_DRIF_RFOVF;
+ writel(str, sch->base + RCAR_DRIF_SISTR);
+
+ /* Enable DMA receive interrupt */
+ writel(0x00009000, sch->base + RCAR_DRIF_SIIER);
+
+ return ret;
+}
+
+/* Start receive operation */
+static int rcar_drif_start(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+ int ret;
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ ret = rcar_drif_start_subchan(ch->sch[i]);
+ if (ret)
+ goto start_error;
+ }
+
+ ch->produced = 0;
+
+ ret = rcar_drif_enable_rx(ch);
+ if (ret)
+ goto start_error;
+
+ rdrif_dbg(1, ch, "started\n");
+
+start_error:
+ return ret;
+}
+
+/* Stop sub-channel */
+static void rcar_drif_stop_subchan(struct rcar_drif_subchan *sch)
+{
+ struct rcar_drif_chan *ch = sch->parent;
+ int ret, retries = 3;
+
+ /* Disable DMA receive interrupt */
+ writel(0x00000000, sch->base + RCAR_DRIF_SIIER);
+
+ do {
+ /* Terminate all DMA transfers */
+ ret = dmaengine_terminate_sync(sch->dmach);
+ if (!ret)
+ break;
+ rdrif_dbg(2, ch, "stop retry\n");
+ } while (--retries);
+
+ WARN_ON(!retries);
+}
+
+/* Stop receive operation */
+static void rcar_drif_stop(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+
+ /* Disable Rx */
+ rcar_drif_disable_rx(ch);
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask)
+ rcar_drif_stop_subchan(ch->sch[i]);
+
+ rdrif_dbg(1, ch, "stopped: prod %u\n", ch->produced);
+}
+
+/* Start streaming */
+static int rcar_drif_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct rcar_drif_chan *ch = vb2_get_drv_priv(vq);
+ unsigned int i, j;
+ int ret;
+
+ rdrif_dbg(1, ch, "start streaming\n");
+ mutex_lock(&ch->v4l2_mutex);
+
+ for_each_rcar_drif_subchannel(i, &ch->cur_schans_mask) {
+ ret = clk_prepare_enable(ch->sch[i]->clkp);
+ if (ret)
+ goto start_error;
+ }
+
+ /* Set default MDRx settings */
+ rcar_drif_set_mdr1(ch);
+
+ /* Set new format */
+ ret = rcar_drif_set_format(ch);
+ if (ret)
+ goto start_error;
+
+ if (ch->num_cur_schans == RCAR_DRIF_MAX_SUBCHANS)
+ ch->hwbuf_size = formats[ch->fmt_idx].buffersize/2;
+ else
+ ch->hwbuf_size = formats[ch->fmt_idx].buffersize;
+
+ rdrif_dbg(1, ch, "num_hwbufs %u, hwbuf_size %u\n",
+ ch->num_hwbufs, ch->hwbuf_size);
+
+ /* Alloc DMA channel */
+ ret = rcar_drif_alloc_dmachannel(ch);
+ if (ret)
+ goto start_error;
+
+ /* Alloc buf context */
+ ret = rcar_drif_alloc_bufctxt(ch);
+ if (ret)
+ goto start_error;
+
+ /* Request buffers */
+ ret = rcar_drif_request_buf(ch);
+ if (ret)
+ goto start_error;
+
+ /* Start Rx */
+ ret = rcar_drif_start(ch);
+ if (ret)
+ goto start_error;
+
+ mutex_unlock(&ch->v4l2_mutex);
+ return ret;
+
+start_error:
+ rcar_drif_release_queued_bufs(ch, VB2_BUF_STATE_QUEUED);
+ rcar_drif_release_buf(ch);
+ rcar_drif_release_bufctxt(ch);
+ rcar_drif_release_dmachannel(ch);
+ for (j = 0; j < i; j++)
+ clk_disable_unprepare(ch->sch[j]->clkp);
+
+ mutex_unlock(&ch->v4l2_mutex);
+ return ret;
+}
+
+/* Stop streaming */
+static void rcar_drif_stop_streaming(struct vb2_queue *vq)
+{
+ struct rcar_drif_chan *ch = vb2_get_drv_priv(vq);
+ unsigned int i;
+
+ mutex_lock(&ch->v4l2_mutex);
+
+ /* Stop hardware streaming */
+ rcar_drif_stop(ch);
+
+ /* Return all queued buffers to vb2 */
+ rcar_drif_release_queued_bufs(ch, VB2_BUF_STATE_ERROR);
+
+ /* Release buf & buf context */
+ rcar_drif_release_buf(ch);
+ rcar_drif_release_bufctxt(ch);
+
+ /* Release DMA channel resources */
+ rcar_drif_release_dmachannel(ch);
+
+ for (i = 0; i < RCAR_DRIF_MAX_SUBCHANS; i++)
+ clk_disable_unprepare(ch->sch[i]->clkp);
+
+ mutex_unlock(&ch->v4l2_mutex);
+ rdrif_dbg(1, ch, "stopped streaming\n");
+}
+
+/* Vb2 ops */
+static struct vb2_ops rcar_drif_vb2_ops = {
+ .queue_setup = rcar_drif_queue_setup,
+ .buf_queue = rcar_drif_buf_queue,
+ .start_streaming = rcar_drif_start_streaming,
+ .stop_streaming = rcar_drif_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int rcar_drif_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+
+ strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+ strlcpy(cap->card, ch->vdev.name, sizeof(cap->card));
+ cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_TUNER |
+ V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ ch->vdev.name);
+ return 0;
+}
+
+static int rcar_drif_set_default_format(struct rcar_drif_chan *ch)
+{
+ unsigned int i;
+
+ for (i = 0; i < NUM_FORMATS; i++) {
+ /* Find any matching fmt and set it as default */
+ if (ch->num_hw_schans == formats[i].num_schans) {
+ ch->fmt_idx = i;
+ ch->cur_schans_mask = ch->hw_schans_mask;
+ ch->num_cur_schans = ch->num_hw_schans;
+ dev_dbg(ch->dev, "default fmt[%u]: mask %lu num %u\n",
+ i, ch->cur_schans_mask, ch->num_cur_schans);
+ return 0;
+ }
+ }
+ dev_err(ch->dev, "no matching sdr fmt found\n");
+ return -EINVAL;
+}
+
+static int rcar_drif_enum_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index >= NUM_FORMATS)
+ return -EINVAL;
+
+ strlcpy(f->description, formats[f->index].name, sizeof(f->description));
+ f->pixelformat = formats[f->index].pixelformat;
+ return 0;
+}
+
+static int rcar_drif_g_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+
+ f->fmt.sdr.pixelformat = formats[ch->fmt_idx].pixelformat;
+ f->fmt.sdr.buffersize = formats[ch->fmt_idx].buffersize;
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ return 0;
+}
+
+static int rcar_drif_s_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+ struct vb2_queue *q = &ch->vb_queue;
+ unsigned int i;
+
+ if (vb2_is_busy(q))
+ return -EBUSY;
+
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+ ch->fmt_idx = i;
+ f->fmt.sdr.buffersize = formats[i].buffersize;
+
+ /*
+ * If a format demands one sub-channel only out of two
+ * enabled sub-channels then pick the 0th sub-channel
+ */
+ if (formats[i].num_schans < ch->num_hw_schans) {
+ ch->cur_schans_mask = BIT(0); /* Enable D0 */
+ ch->num_cur_schans = formats[i].num_schans;
+ } else {
+ ch->cur_schans_mask = ch->hw_schans_mask;
+ ch->num_cur_schans = ch->num_hw_schans;
+ }
+
+ rdrif_dbg(1, ch, "cur: idx %u mask %lu num %u\n",
+ i, ch->cur_schans_mask, ch->num_cur_schans);
+ return 0;
+ }
+ }
+
+ if (rcar_drif_set_default_format(ch))
+ return -EINVAL;
+
+ f->fmt.sdr.pixelformat = formats[ch->fmt_idx].pixelformat;
+ f->fmt.sdr.buffersize = formats[ch->fmt_idx].buffersize;
+ return 0;
+}
+
+static int rcar_drif_try_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+ unsigned int i;
+
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+ f->fmt.sdr.buffersize = formats[i].buffersize;
+ return 0;
+ }
+ }
+
+ f->fmt.sdr.pixelformat = formats[ch->fmt_idx].pixelformat;
+ f->fmt.sdr.buffersize = formats[ch->fmt_idx].buffersize;
+ return 0;
+}
+
+/* Tuner subdev ioctls */
+static int rcar_drif_enum_freq_bands(struct file *file, void *priv,
+ struct v4l2_frequency_band *band)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+ struct v4l2_subdev *sd, *tmp;
+ int ret = 0;
+
+ for_each_rcar_drif_subdev(sd, tmp, ch) {
+ ret = v4l2_subdev_call(sd, tuner, enum_freq_bands, band);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static int rcar_drif_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+ struct v4l2_subdev *sd, *tmp;
+ int ret = 0;
+
+ for_each_rcar_drif_subdev(sd, tmp, ch) {
+ ret = v4l2_subdev_call(sd, tuner, g_frequency, f);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static int rcar_drif_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *f)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+ struct v4l2_subdev *sd, *tmp;
+ int ret = 0;
+
+ for_each_rcar_drif_subdev(sd, tmp, ch) {
+ ret = v4l2_subdev_call(sd, tuner, s_frequency, f);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static int rcar_drif_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *vt)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+ struct v4l2_subdev *sd, *tmp;
+ int ret = 0;
+
+ for_each_rcar_drif_subdev(sd, tmp, ch) {
+ ret = v4l2_subdev_call(sd, tuner, g_tuner, vt);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static int rcar_drif_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *vt)
+{
+ struct rcar_drif_chan *ch = video_drvdata(file);
+ struct v4l2_subdev *sd, *tmp;
+ int ret = 0;
+
+ for_each_rcar_drif_subdev(sd, tmp, ch) {
+ ret = v4l2_subdev_call(sd, tuner, s_tuner, vt);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static const struct v4l2_ioctl_ops rcar_drif_ioctl_ops = {
+ .vidioc_querycap = rcar_drif_querycap,
+
+ .vidioc_enum_fmt_sdr_cap = rcar_drif_enum_fmt_sdr_cap,
+ .vidioc_g_fmt_sdr_cap = rcar_drif_g_fmt_sdr_cap,
+ .vidioc_s_fmt_sdr_cap = rcar_drif_s_fmt_sdr_cap,
+ .vidioc_try_fmt_sdr_cap = rcar_drif_try_fmt_sdr_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_s_frequency = rcar_drif_s_frequency,
+ .vidioc_g_frequency = rcar_drif_g_frequency,
+ .vidioc_s_tuner = rcar_drif_s_tuner,
+ .vidioc_g_tuner = rcar_drif_g_tuner,
+ .vidioc_enum_freq_bands = rcar_drif_enum_freq_bands,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_log_status = v4l2_ctrl_log_status,
+};
+
+static const struct v4l2_file_operations rcar_drif_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static struct video_device rcar_drif_vdev = {
+ .name = "R-Car DRIF",
+ .release = video_device_release_empty,
+ .fops = &rcar_drif_fops,
+ .ioctl_ops = &rcar_drif_ioctl_ops,
+};
+
+static int rcar_drif_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rcar_drif_chan *ch =
+ container_of(notifier, struct rcar_drif_chan, notifier);
+
+ /* Nothing to do at this point */
+ rdrif_dbg(2, ch, "bound asd: %s\n", asd->match.of.node->name);
+ return 0;
+}
+
+/* Sub-device registered notification callback */
+static int rcar_drif_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct rcar_drif_chan *ch =
+ container_of(notifier, struct rcar_drif_chan, notifier);
+ struct v4l2_subdev *sd, *tmp;
+ int ret;
+
+ v4l2_ctrl_handler_init(&ch->ctrl_hdl, 10);
+ ch->v4l2_dev.ctrl_handler = &ch->ctrl_hdl;
+
+ ret = v4l2_device_register_subdev_nodes(&ch->v4l2_dev);
+ if (ret) {
+ rdrif_err(ch, "failed register subdev nodes ret %d\n", ret);
+ return ret;
+ }
+
+ for_each_rcar_drif_subdev(sd, tmp, ch) {
+ ret = v4l2_ctrl_add_handler(ch->v4l2_dev.ctrl_handler,
+ sd->ctrl_handler, NULL);
+ if (ret) {
+ rdrif_err(ch, "failed ctrl add hdlr ret %d\n", ret);
+ return ret;
+ }
+ }
+ rdrif_dbg(2, ch, "notify complete\n");
+ return 0;
+}
+
+/* Parse sub-devs (tuner) to find a matching device */
+static int rcar_drif_parse_subdevs(struct device *dev,
+ struct v4l2_async_notifier *notifier)
+{
+ struct device_node *node = NULL;
+
+ notifier->subdevs = devm_kzalloc(dev, sizeof(*notifier->subdevs),
+ GFP_KERNEL);
+ if (!notifier->subdevs)
+ return -ENOMEM;
+
+ node = of_graph_get_next_endpoint(dev->of_node, node);
+ if (node) {
+ struct rcar_drif_async_subdev *rsd;
+
+ rsd = devm_kzalloc(dev, sizeof(*rsd), GFP_KERNEL);
+ if (!rsd) {
+ of_node_put(node);
+ return -ENOMEM;
+ }
+
+ notifier->subdevs[notifier->num_subdevs] = &rsd->asd;
+ rsd->asd.match.of.node = of_graph_get_remote_port_parent(node);
+ of_node_put(node);
+ if (!rsd->asd.match.of.node) {
+ dev_warn(dev, "bad remote port parent\n");
+ return -EINVAL;
+ }
+
+ rsd->asd.match_type = V4L2_ASYNC_MATCH_OF;
+ notifier->num_subdevs++;
+ }
+ return 0;
+}
+
+/* SIRMDR1 configuration */
+static int rcar_drif_validate_syncmd(struct rcar_drif_chan *ch, u32 val)
+{
+ if (val > 1) {
+ dev_err(ch->dev, "invalid syncmd %u using L/R mode\n", val);
+ return -EINVAL;
+ }
+
+ ch->mdr1 &= ~(3 << 28); /* Clear current settings */
+ if (val == 0)
+ ch->mdr1 |= RCAR_DRIF_SIRMDR1_SYNCMD_FRAME;
+ else
+ ch->mdr1 |= RCAR_DRIF_SIRMDR1_SYNCMD_LR;
+ return 0;
+}
+
+/* Get the dtdl or syncdl bits as in MSIOF */
+static u32 rcar_drif_get_dtdl_or_syncdl_bits(u32 dtdl_or_syncdl)
+{
+ /*
+ * DTDL/SYNCDL bit : dtdl/syncdl
+ * b'000 : 0
+ * b'001 : 100
+ * b'010 : 200
+ * b'011 (SYNCDL only) : 300
+ * b'101 : 50
+ * b'110 : 150
+ */
+ if (dtdl_or_syncdl % 100)
+ return dtdl_or_syncdl / 100 + 5;
+ else
+ return dtdl_or_syncdl / 100;
+}
+
+static int rcar_drif_validate_dtdl_syncdl(struct rcar_drif_chan *ch)
+{
+ struct device_node *np = ch->dev->of_node;
+ u32 dtdl = 100, syncdl = 0;
+
+ ch->mdr1 |= RCAR_DRIF_SIRMDR1_DTDL_1 | RCAR_DRIF_SIRMDR1_SYNCDL_0;
+ of_property_read_u32(np, "renesas,dtdl", &dtdl);
+ of_property_read_u32(np, "renesas,syncdl", &syncdl);
+
+ /* Sanity checks */
+ if (dtdl > 200 || syncdl > 300) {
+ dev_err(ch->dev, "invalid dtdl %u/syncdl %u\n", dtdl, syncdl);
+ return -EINVAL;
+ }
+ if ((dtdl + syncdl) % 100) {
+ dev_err(ch->dev, "sum of dtdl %u & syncdl %u not OK\n",
+ dtdl, syncdl);
+ return -EINVAL;
+ }
+ ch->mdr1 &= ~(7 << 20) & ~(7 << 16); /* Clear current settings */
+ ch->mdr1 |= rcar_drif_get_dtdl_or_syncdl_bits(dtdl) << 20;
+ ch->mdr1 |= rcar_drif_get_dtdl_or_syncdl_bits(syncdl) << 16;
+ return 0;
+}
+
+static int rcar_drif_parse_properties(struct rcar_drif_chan *ch)
+{
+ struct device_node *np = ch->dev->of_node;
+ u32 syncmd;
+ int ret;
+
+ /* Set the defaults and check for overrides */
+ ch->mdr1 = RCAR_DRIF_SIRMDR1_SYNCMD_LR;
+ if (!of_property_read_u32(np, "renesas,syncmd", &syncmd)) {
+ ret = rcar_drif_validate_syncmd(ch, syncmd);
+ if (ret)
+ return ret;
+ }
+
+ if (of_find_property(np, "renesas,lsb-first", NULL))
+ ch->mdr1 |= RCAR_DRIF_SIRMDR1_LSB_FIRST;
+ else
+ ch->mdr1 |= RCAR_DRIF_SIRMDR1_MSB_FIRST;
+
+ if (of_find_property(np, "renesas,syncac-pol-high", NULL))
+ ch->mdr1 |= RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH;
+ else
+ ch->mdr1 |= RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW;
+
+ return rcar_drif_validate_dtdl_syncdl(ch);
+}
+
+static u32 rcar_drif_enum_sub_channels(struct platform_device *pdev,
+ struct platform_device *s_pdev[])
+{
+ struct device_node *s_np;
+ u32 hw_schans_mask = 0;
+ unsigned int i;
+
+ for (i = 0; i < RCAR_DRIF_MAX_SUBCHANS; i++) {
+ s_np = of_parse_phandle(pdev->dev.of_node, "sub-channels", i);
+ if (s_np && of_device_is_available(s_np)) {
+ s_pdev[i] = of_find_device_by_node(s_np);
+ if (s_pdev[i]) {
+ hw_schans_mask |= BIT(i);
+ dev_dbg(&s_pdev[i]->dev, "schan%u ok\n", i);
+ }
+ }
+ }
+ return hw_schans_mask;
+}
+
+static int rcar_drif_probe(struct platform_device *pdev)
+{
+ struct platform_device *s_pdev[RCAR_DRIF_MAX_SUBCHANS];
+ unsigned long hw_schans_mask;
+ struct rcar_drif_chan *ch;
+ unsigned int i;
+ int ret;
+
+ /*
+ * Sub-channel resources are managed by the parent channel instance.
+ * The sub-channel instance helps only in registering with power domain
+ * to aid in run-time pm support
+ */
+ if (!of_find_property(pdev->dev.of_node, "sub-channels", NULL))
+ return 0;
+
+ /* Parent channel instance */
+ hw_schans_mask = rcar_drif_enum_sub_channels(pdev, s_pdev);
+ if (!hw_schans_mask) {
+ dev_err(&pdev->dev, "no sub-channels enabled\n");
+ return -ENODEV;
+ }
+
+
+ /* Reserve memory for driver structure */
+ ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL);
+ if (!ch) {
+ ret = PTR_ERR(ch);
+ dev_err(&pdev->dev, "failed alloc drif context\n");
+ return ret;
+ }
+ ch->dev = &pdev->dev;
+
+ /* Parse device tree optional properties */
+ ret = rcar_drif_parse_properties(ch);
+ if (ret)
+ return ret;
+
+ dev_dbg(ch->dev, "parsed mdr1 0x%08x\n", ch->mdr1);
+
+ /* Setup enabled sub-channels */
+ for_each_rcar_drif_subchannel(i, &hw_schans_mask) {
+ struct clk *clkp;
+ struct resource *res;
+ void __iomem *base;
+
+ /* Peripheral clock */
+ clkp = devm_clk_get(&s_pdev[i]->dev, "fck");
+ if (IS_ERR(clkp)) {
+ ret = PTR_ERR(clkp);
+ dev_err(&s_pdev[i]->dev, "clk get failed (%d)\n", ret);
+ return ret;
+ }
+
+ /* Register map */
+ res = platform_get_resource(s_pdev[i], IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(&s_pdev[i]->dev, res);
+ if (IS_ERR(base)) {
+ ret = PTR_ERR(base);
+ dev_err(&s_pdev[i]->dev, "ioremap failed (%d)\n", ret);
+ return ret;
+ }
+
+ /* Reserve memory for enabled sub-channel */
+ ch->sch[i] = devm_kzalloc(&pdev->dev, sizeof(*ch->sch[i]),
+ GFP_KERNEL);
+ if (!ch->sch[i]) {
+ ret = PTR_ERR(ch);
+ dev_err(&s_pdev[i]->dev, "failed alloc sub-channel\n");
+ return ret;
+ }
+ ch->sch[i]->pdev = s_pdev[i];
+ ch->sch[i]->clkp = clkp;
+ ch->sch[i]->base = base;
+ ch->sch[i]->num = i;
+ ch->sch[i]->start = res->start;
+ ch->sch[i]->parent = ch;
+ ch->num_hw_schans++;
+ }
+ ch->hw_schans_mask = hw_schans_mask;
+
+ /* Validate any supported format for enabled sub-channels */
+ ret = rcar_drif_set_default_format(ch);
+ if (ret)
+ return ret;
+
+ /* Set defaults */
+ ch->num_hwbufs = RCAR_DRIF_DEFAULT_NUM_HWBUFS;
+ ch->hwbuf_size = RCAR_DRIF_DEFAULT_HWBUF_SIZE;
+
+ mutex_init(&ch->v4l2_mutex);
+ mutex_init(&ch->vb_queue_mutex);
+ spin_lock_init(&ch->queued_bufs_lock);
+ INIT_LIST_HEAD(&ch->queued_bufs);
+
+ /* Init videobuf2 queue structure */
+ ch->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
+ ch->vb_queue.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF;
+ ch->vb_queue.drv_priv = ch;
+ ch->vb_queue.buf_struct_size = sizeof(struct rcar_drif_frame_buf);
+ ch->vb_queue.ops = &rcar_drif_vb2_ops;
+ ch->vb_queue.mem_ops = &vb2_vmalloc_memops;
+ ch->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+ /* Init videobuf2 queue */
+ ret = vb2_queue_init(&ch->vb_queue);
+ if (ret) {
+ dev_err(ch->dev, "could not initialize vb2 queue\n");
+ return ret;
+ }
+
+ /* Init video_device structure */
+ ch->vdev = rcar_drif_vdev;
+ ch->vdev.lock = &ch->v4l2_mutex;
+ ch->vdev.queue = &ch->vb_queue;
+ ch->vdev.queue->lock = &ch->vb_queue_mutex;
+ ch->vdev.ctrl_handler = &ch->ctrl_hdl;
+ video_set_drvdata(&ch->vdev, ch);
+
+ /* Register the v4l2_device */
+ ret = v4l2_device_register(&pdev->dev, &ch->v4l2_dev);
+ if (ret) {
+ dev_err(ch->dev, "failed v4l2_device_register. ret %d\n", ret);
+ return ret;
+ }
+
+ ch->vdev.v4l2_dev = &ch->v4l2_dev;
+
+ /*
+ * Parse subdevs after v4l2_device_register because if the subdev
+ * is already probed, bound and complete will be called immediately
+ */
+ ret = rcar_drif_parse_subdevs(&pdev->dev, &ch->notifier);
+ if (ret)
+ goto err_unreg_v4l2;
+
+ ch->notifier.bound = rcar_drif_notify_bound;
+ ch->notifier.complete = rcar_drif_notify_complete;
+
+ /* Register notifier */
+ ret = v4l2_async_notifier_register(&ch->v4l2_dev, &ch->notifier);
+ if (ret < 0) {
+ dev_err(ch->dev, "notifier registration failed\n");
+ goto err_unreg_v4l2;
+ }
+
+ /* Register SDR device */
+ ret = video_register_device(&ch->vdev, VFL_TYPE_SDR, -1);
+ if (ret) {
+ dev_err(ch->dev, "failed video_register_device. ret %d\n", ret);
+ goto err_unreg_notif;
+ }
+
+ platform_set_drvdata(pdev, ch);
+ dev_notice(ch->dev, "probed\n");
+ return 0;
+
+err_unreg_notif:
+ v4l2_async_notifier_unregister(&ch->notifier);
+err_unreg_v4l2:
+ v4l2_device_unregister(&ch->v4l2_dev);
+ return ret;
+}
+
+static int rcar_drif_remove(struct platform_device *pdev)
+{
+ struct rcar_drif_chan *ch = platform_get_drvdata(pdev);
+
+ if (!ch)
+ return 0;
+
+ /* Parent channel instance */
+ ch = platform_get_drvdata(pdev);
+ v4l2_ctrl_handler_free(ch->v4l2_dev.ctrl_handler);
+ v4l2_async_notifier_unregister(&ch->notifier);
+ v4l2_device_unregister(&ch->v4l2_dev);
+ video_unregister_device(&ch->vdev);
+ dev_notice(ch->dev, "removed\n");
+ return 0;
+}
+
+static int __maybe_unused rcar_drif_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int __maybe_unused rcar_drif_resume(struct device *dev)
+{
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rcar_drif_pm_ops, rcar_drif_suspend,
+ rcar_drif_resume);
+
+static const struct of_device_id rcar_drif_of_table[] = {
+ { .compatible = "renesas,rcar-gen3-drif" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rcar_drif_of_table);
+
+#define RCAR_DRIF_DRV_NAME "rcar_drif"
+static struct platform_driver rcar_drif_driver = {
+ .driver = {
+ .name = RCAR_DRIF_DRV_NAME,
+ .of_match_table = of_match_ptr(rcar_drif_of_table),
+ .pm = &rcar_drif_pm_ops,
+ },
+ .probe = rcar_drif_probe,
+ .remove = rcar_drif_remove,
+};
+
+module_platform_driver(rcar_drif_driver);
+
+MODULE_DESCRIPTION("Renesas R-Car Gen3 DRIF driver");
+MODULE_ALIAS("platform:" RCAR_DRIF_DRV_NAME);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>");
--
1.9.1
^ permalink raw reply related
* [PATCH 3/5] media: Add new SDR formats SC16, SC18 & SC20
From: Ramesh Shanmugasundaram @ 2016-11-09 15:44 UTC (permalink / raw)
To: robh+dt, mark.rutland, mchehab, hverkuil, sakari.ailus, crope
Cc: chris.paterson2, laurent.pinchart, geert+renesas, linux-media,
devicetree, linux-renesas-soc, Ramesh Shanmugasundaram
In-Reply-To: <1478706284-59134-1-git-send-email-ramesh.shanmugasundaram@bp.renesas.com>
This patch adds support for the three new SDR formats. These formats
were prefixed with "sliced" indicating I data constitutes the top half and
Q data constitutes the bottom half of the received buffer.
V4L2_SDR_FMT_SCU16BE - 14-bit complex (I & Q) unsigned big-endian sample
inside 16-bit. V4L2 FourCC: SC16
V4L2_SDR_FMT_SCU18BE - 16-bit complex (I & Q) unsigned big-endian sample
inside 18-bit. V4L2 FourCC: SC18
V4L2_SDR_FMT_SCU20BE - 18-bit complex (I & Q) unsigned big-endian sample
inside 20-bit. V4L2 FourCC: SC20
Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
---
drivers/media/v4l2-core/v4l2-ioctl.c | 3 +++
include/uapi/linux/videodev2.h | 3 +++
2 files changed, 6 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index 181381d..d36b386 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1207,6 +1207,9 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
case V4L2_SDR_FMT_CS8: descr = "Complex S8"; break;
case V4L2_SDR_FMT_CS14LE: descr = "Complex S14LE"; break;
case V4L2_SDR_FMT_RU12LE: descr = "Real U12LE"; break;
+ case V4L2_SDR_FMT_SCU16BE: descr = "Sliced Complex U16BE"; break;
+ case V4L2_SDR_FMT_SCU18BE: descr = "Sliced Complex U18BE"; break;
+ case V4L2_SDR_FMT_SCU20BE: descr = "Sliced Complex U20BE"; break;
case V4L2_TCH_FMT_DELTA_TD16: descr = "16-bit signed deltas"; break;
case V4L2_TCH_FMT_DELTA_TD08: descr = "8-bit signed deltas"; break;
case V4L2_TCH_FMT_TU16: descr = "16-bit unsigned touch data"; break;
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 4364ce6..34a9c30 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -666,6 +666,9 @@ struct v4l2_pix_format {
#define V4L2_SDR_FMT_CS8 v4l2_fourcc('C', 'S', '0', '8') /* complex s8 */
#define V4L2_SDR_FMT_CS14LE v4l2_fourcc('C', 'S', '1', '4') /* complex s14le */
#define V4L2_SDR_FMT_RU12LE v4l2_fourcc('R', 'U', '1', '2') /* real u12le */
+#define V4L2_SDR_FMT_SCU16BE v4l2_fourcc('S', 'C', '1', '6') /* sliced complex u16be */
+#define V4L2_SDR_FMT_SCU18BE v4l2_fourcc('S', 'C', '1', '8') /* sliced complex u18be */
+#define V4L2_SDR_FMT_SCU20BE v4l2_fourcc('S', 'C', '2', '0') /* sliced complex u20be */
/* Touch formats - used for Touch devices */
#define V4L2_TCH_FMT_DELTA_TD16 v4l2_fourcc('T', 'D', '1', '6') /* 16-bit signed deltas */
--
1.9.1
^ permalink raw reply related
* [PATCH 1/5] media: v4l2-ctrls: Reserve controls for MAX217X
From: Ramesh Shanmugasundaram @ 2016-11-09 15:44 UTC (permalink / raw)
To: robh+dt, mark.rutland, mchehab, hverkuil, sakari.ailus, crope
Cc: chris.paterson2, laurent.pinchart, geert+renesas, linux-media,
devicetree, linux-renesas-soc, Ramesh Shanmugasundaram
In-Reply-To: <1478706284-59134-1-git-send-email-ramesh.shanmugasundaram@bp.renesas.com>
Reserve controls for MAX217X RF to Bits tuner (family) chips. These hybrid
radio receiver chips are highly programmable and hence reserving 32
controls.
Signed-off-by: Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>
---
include/uapi/linux/v4l2-controls.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index b6a357a..b7404c9 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -180,6 +180,11 @@ enum v4l2_colorfx {
* We reserve 16 controls for this driver. */
#define V4L2_CID_USER_TC358743_BASE (V4L2_CID_USER_BASE + 0x1080)
+/* The base for the max217x driver controls.
+ * We reserve 32 controls for this driver
+ */
+#define V4L2_CID_USER_MAX217X_BASE (V4L2_CID_USER_BASE + 0x1090)
+
/* MPEG-class control IDs */
/* The MPEG controls are applicable to all codec controls
* and the 'MPEG' part of the define is historical */
--
1.9.1
^ permalink raw reply related
* Re: Shouldn't VFIO virtualize the ATS capability?
From: Alex Williamson @ 2016-11-09 15:53 UTC (permalink / raw)
To: Ilya Lesokhin
Cc: linux-pci@vger.kernel.org, kvm@vger.kernel.org,
bhelgaas@google.com, Adi Menachem
In-Reply-To: <VI1PR0502MB2957CCD1E8EE93A3BC6CE178D4B90@VI1PR0502MB2957.eurprd05.prod.outlook.com>
On Wed, 9 Nov 2016 15:25:16 +0000
Ilya Lesokhin <ilyal@mellanox.com> wrote:
> > -----Original Message-----
> > From: Alex Williamson [mailto:alex.williamson@redhat.com]
> > Sent: Wednesday, November 09, 2016 5:08 PM
> > To: Ilya Lesokhin <ilyal@mellanox.com>
> > Cc: linux-pci@vger.kernel.org; kvm@vger.kernel.org; bhelgaas@google.com;
> > Adi Menachem <adim@mellanox.com>
> > Subject: Re: Shouldn't VFIO virtualize the ATS capability?
> >
> > On Wed, 9 Nov 2016 14:49:02 +0000
> > Ilya Lesokhin <ilyal@mellanox.com> wrote:
> >
> > > I would virtualize the "ATS Control Register".
> >
> > And do what?
> I think it should be read only.
That would violate the spec, in which case it shouldn't be virtualized,
the capability should be hidden.
> > > Regarding poor behavior, I couldn't really find what happens when ATS is
> > misconfigured, but I would assume it can cause problems.
> > > The scenarios I'm concerned about are:
> > > 1. The guest enables translation caching, while the hypervisor thinks
> > there are disabled -> Hypervisor won't issue invalidations.
> >
> > Aren't invalidations issued by the iommu, why does the hypervisor need to
> > participate? How would a software entity induce an invalidation?
> That's what one might expect from a sane design, but
> http://lxr.free-electrons.com/source/drivers/iommu/intel-iommu.c?v=4.8#L1549
> seems to imply otherwise :(
> >
> > > 2. Smallest Translation Unit misconfiguration. Not sure if it will cause
> > invalid access or only poor caching behavior.
> > >
> > > Thanks,
> > > Ilya
> > >
> > > > -----Original Message-----
> > > > From: Alex Williamson [mailto:alex.williamson@redhat.com]
> > > > Sent: Sunday, November 06, 2016 7:09 PM
> > > > To: Ilya Lesokhin <ilyal@mellanox.com>
> > > > Cc: linux-pci@vger.kernel.org; kvm@vger.kernel.org;
> > > > bhelgaas@google.com
> > > > Subject: Re: Shouldn't VFIO virtualize the ATS capability?
> > > >
> > > > On Sun, 6 Nov 2016 11:13:09 +0000
> > > > Ilya Lesokhin <ilyal@mellanox.com> wrote:
> > > >
> > > > > Hi
> > > > > I've noticed that VFIO doesn't virtualize the ATS capability.
> > > > > It seems to me that translation caching and Smallest Translation
> > > > > Unit is
> > > > something you would want to control on the host. Am I wrong?
> > > >
> > > > What about those fields would we virtualize? Why does the host need
> > > > to be an intermediary? Can the user induce poor behavior with
> > > > direct access to them? Thanks,
> > > >
> > > > Alex
>
^ permalink raw reply
* Re: [Patch V6 2/6] irqchip: xilinx: Clean up irqdomain argument and read/write
From: Marc Zyngier @ 2016-11-09 15:53 UTC (permalink / raw)
To: Zubair Lutfullah Kakakhel, Thomas Gleixner
Cc: monstr, jason, linux-kernel, michal.simek, linuxppc-dev, mpe
In-Reply-To: <fea1e962-1af2-d666-2b0d-55a8e230430c@imgtec.com>
On 01/11/16 11:05, Zubair Lutfullah Kakakhel wrote:
> Hi,
>
> Thanks for the review.
>
> On 10/31/2016 07:51 PM, Thomas Gleixner wrote:
>> On Mon, 31 Oct 2016, Zubair Lutfullah Kakakhel wrote:
>>> The drivers read/write function handling is a bit quirky.
>>
>> Can you please explain in more detail what's quirky and why it should be
>> done differently,
>>
>>> And the irqmask is passed directly to the handler.
>>
>> I can't make any sense out of that sentence. Which handler? If you talk
>> about the write function, then I don't see a change. So what are you
>> talking about?
>
> Thanks. I'll add more detail in v7 if this patch survives.
>
>>
>>> Add a new irqchip struct to pass to the handler and
>>> cleanup read/write handling.
>>
>> I still don't see what it cleans up. You move the write function pointer
>> into a data structure, which is exposed by another pointer. So you create
>> two levels of indirection in the hotpath. The function prototype is still
>> the same. So all this does is making things slower unless I'm missing
>> something.
>
> I wrote this patch/cleanup based on a review of driver by Marc when I moved the
> driver from arch/microblaze to drivers/irqchip
>
> "Marc Zyngier
>
> ...
>
> > arch/microblaze/kernel/intc.c | 196 ----------------------------------------
> > drivers/irqchip/irq-axi-intc.c | 196 ++++++++++++++++++++++++++++++++++++++++
>
> ...
>
> > + /* Yeah, okay, casting the intr_mask to a void* is butt-ugly, but I'm
> > + * lazy and Michal can clean it up to something nicer when he tests
> > + * and commits this patch. ~~gcl */
> > + root_domain = irq_domain_add_linear(intc, nr_irq, &xintc_irq_domain_ops,
> > + (void *)intr_mask);
>
> Since you're now reworking this driver, how about addressing this
> ugliness? You could store the intr_mask together with intc_baseaddr,
> and the read/write functions in a global structure, and pass a
> pointer to it? That would make the code a bit nicer...
> "
>
> https://patchwork.kernel.org/patch/9287933/
>
>>
>>> -static unsigned int (*read_fn)(void __iomem *);
>>> -static void (*write_fn)(u32, void __iomem *);
>>> +struct xintc_irq_chip {
>>> + void __iomem *base;
>>> + struct irq_domain *domain;
>>> + struct irq_chip chip;
>>
>> The tabs between struct and the structure name are bogus.
>>
>>> + u32 intr_mask;
>>> + unsigned int (*read)(void __iomem *iomem);
>>> + void (*write)(u32 data, void __iomem *iomem);
>>
>> Please structure that like a table:
>>
>> void __iomem *base;
>> struct irq_domain *domain;
>> struct irq_chip chip;
>> u32 intr_mask;
>> unsigned int (*read)(void __iomem *iomem);
>> void (*write)(u32 data, void __iomem *iomem);
>>
>> Can you see how that makes parsing the struct simpler, because the data
>> types are clearly to identify?
>
> That does make it look much better.
>
>>
>>> +static struct xintc_irq_chip *xintc_irqc;
>>>
>>> static void intc_write32(u32 val, void __iomem *addr)
>>> {
>>> @@ -54,6 +60,18 @@ static unsigned int intc_read32_be(void __iomem *addr)
>>> return ioread32be(addr);
>>> }
>>>
>>> +static inline unsigned int xintc_read(struct xintc_irq_chip *xintc_irqc,
>>> + int reg)
>>> +{
>>> + return xintc_irqc->read(xintc_irqc->base + reg);
>>> +}
>>> +
>>> +static inline void xintc_write(struct xintc_irq_chip *xintc_irqc,
>>> + int reg, u32 data)
>>> +{
>>> + xintc_irqc->write(data, xintc_irqc->base + reg);
>>> +}
>>> +
>>> static void intc_enable_or_unmask(struct irq_data *d)
>>> {
>>> unsigned long mask = 1 << d->hwirq;
>>> @@ -65,21 +83,21 @@ static void intc_enable_or_unmask(struct irq_data *d)
>>> * acks the irq before calling the interrupt handler
>>> */
>>> if (irqd_is_level_type(d))
>>> - write_fn(mask, intc_baseaddr + IAR);
>>> + xintc_write(xintc_irqc, IAR, mask);
>>
>> So this whole thing makes only sense, when you want to support multiple
>> instances of that chip and then you need to store the xintc_irqc pointer as
>> irqchip data and retrieve it from there. Unless you do that, this "cleanup"
>> is just churn for nothing with the effect of making things less efficient.
>>
>
> Indeed the driver doesn't support multiple instances of the Xilinx Interrupt controller.
> I don't have a use-case or the hardware for that.
>
> So what would be the recommended course of action?
If you really don't want/need to support multi-instance, then this is
indeed overkill. You're then better off having a simple static key that
deals with the endianess of the peripheral, and have a global structure
that contains the relevant data:
static DEFINE_STATIC_KEY_FALSE(xintc_is_be);
static void xintc_write(int reg, u32 data)
{
if (static_branch_unlikely(&xintc_is_be))
iowrite32be(data, xint_irqc.base + reg);
else
iowrite32(data, xint_irqc.base + reg);
}
and something similar for the write accessor.
Thanks,
M.
--
Jazz is not dead. It just smells funny...
^ permalink raw reply
* Re: [PATCH 1/2] backlight: arcxcnn: add support for ArticSand devices
From: Lee Jones @ 2016-11-09 15:53 UTC (permalink / raw)
To: Olimpiu Dejeu
Cc: robh-DgEjT+Ai2ygdnm+yROfE0A, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, jg1.han-Sze3O3UU22JBDgjK7y7TUQ
In-Reply-To: <1477513778-31297-1-git-send-email-olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org>
Jingoo?
On Wed, 26 Oct 2016, Olimpiu Dejeu wrote:
> Resubmition of arcxcnn backliught driver adding devicetree entries
> for all registers
>
> Signed-off-by: Olimpiu Dejeu <olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org>
>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 1 +
> drivers/video/backlight/arcxcnn_bl.c | 541 +++++++++++++++++++++++++++++++++++
> include/linux/i2c/arcxcnn.h | 67 +++++
> 4 files changed, 616 insertions(+)
> create mode 100644 drivers/video/backlight/arcxcnn_bl.c
> create mode 100644 include/linux/i2c/arcxcnn.h
>
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 5ffa4b4..4e1d2ad 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -460,6 +460,13 @@ config BACKLIGHT_BD6107
> help
> If you have a Rohm BD6107 say Y to enable the backlight driver.
>
> +config BACKLIGHT_ARCXCNN
> + tristate "Backlight driver for the Arctic Sands ARCxCnnnn family"
> + depends on I2C
> + help
> + If you have an ARCxCnnnn family backlight say Y to enable
> + the backlight driver.
> +
> endif # BACKLIGHT_CLASS_DEVICE
>
> endif # BACKLIGHT_LCD_SUPPORT
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index 16ec534..8905129 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -55,3 +55,4 @@ obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o
> obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
> obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
> obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
> +obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o
> diff --git a/drivers/video/backlight/arcxcnn_bl.c b/drivers/video/backlight/arcxcnn_bl.c
> new file mode 100644
> index 0000000..1dad680
> --- /dev/null
> +++ b/drivers/video/backlight/arcxcnn_bl.c
> @@ -0,0 +1,541 @@
> +/*
> + * Backlight driver for ArcticSand ARC_X_C_0N_0N Devices
> + *
> + * Copyright 2016 ArcticSand, Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/backlight.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/pwm.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include "linux/i2c/arcxcnn.h"
> +
> +#define ARCXCNN_CMD (0x00) /* Command Register */
> +#define ARCXCNN_CMD_STDBY (0x80) /* I2C Standby */
> +#define ARCXCNN_CMD_RESET (0x40) /* Reset */
> +#define ARCXCNN_CMD_BOOST (0x10) /* Boost */
> +#define ARCXCNN_CMD_OVP_MASK (0x0C) /* --- Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_XXV (0x0C) /* <rsvrd> Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_20V (0x08) /* 20v Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_24V (0x04) /* 24v Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_31V (0x00) /* 31.4v Over Voltage Threshold */
> +#define ARCXCNN_CMD_EXT_COMP (0x01) /* part (0) or full (1) external comp */
> +
> +#define ARCXCNN_CONFIG (0x01) /* Configuration */
> +#define ARCXCNN_STATUS1 (0x02) /* Status 1 */
> +#define ARCXCNN_STATUS2 (0x03) /* Status 2 */
> +#define ARCXCNN_FADECTRL (0x04) /* Fading Control */
> +#define ARCXCNN_ILED_CONFIG (0x05) /* ILED Configuration */
> +
> +#define ARCXCNN_LEDEN (0x06) /* LED Enable Register */
> +#define ARCXCNN_LEDEN_ISETEXT (0x80) /* Full-scale current set externally */
> +#define ARCXCNN_LEDEN_MASK (0x3F) /* LED string enables */
> +#define ARCXCNN_LEDEN_LED1 (0x01)
> +#define ARCXCNN_LEDEN_LED2 (0x02)
> +#define ARCXCNN_LEDEN_LED3 (0x04)
> +#define ARCXCNN_LEDEN_LED4 (0x08)
> +#define ARCXCNN_LEDEN_LED5 (0x10)
> +#define ARCXCNN_LEDEN_LED6 (0x20)
> +
> +#define ARCXCNN_WLED_ISET_LSB (0x07) /* LED ISET LSB (in upper nibble) */
> +#define ARCXCNN_WLED_ISET_MSB (0x08) /* LED ISET MSB (8 bits) */
> +
> +#define ARCXCNN_DIMFREQ (0x09)
> +#define ARCXCNN_COMP_CONFIG (0x0A)
> +#define ARCXCNN_FILT_CONFIG (0x0B)
> +#define ARCXCNN_IMAXTUNE (0x0C)
> +
> +#define DEFAULT_BL_NAME "arctic_bl"
> +#define MAX_BRIGHTNESS 4095
> +
> +static int s_no_reset_on_remove;
> +module_param_named(noreset, s_no_reset_on_remove, int, 0644);
> +MODULE_PARM_DESC(noreset, "No reset on module removal");
> +
> +static int s_ibright = 60;
> +module_param_named(ibright, s_ibright, int, 0644);
> +MODULE_PARM_DESC(ibright, "Initial brightness (when no plat data)");
> +
> +static int s_iledstr = 0x3F;
> +module_param_named(iledstr, s_iledstr, int, 0644);
> +MODULE_PARM_DESC(iledstr, "Initial LED String (when no plat data)");
> +
> +static int s_retries = 2; /* 1 == only one try */
> +module_param_named(retries, s_retries, int, 0644);
> +MODULE_PARM_DESC(retries, "I2C retries attempted");
> +
> +enum arcxcnn_brightness_ctrl_mode {
> + PWM_BASED = 1,
> + REGISTER_BASED,
> +};
> +
> +struct arcxcnn;
> +
> +struct arcxcnn {
> + char chipname[64];
> + enum arcxcnn_chip_id chip_id;
> + enum arcxcnn_brightness_ctrl_mode mode;
> + struct i2c_client *client;
> + struct backlight_device *bl;
> + struct device *dev;
> + struct arcxcnn_platform_data *pdata;
> + struct pwm_device *pwm;
> + struct regulator *supply; /* regulator for VDD input */
> +};
> +
> +static int arcxcnn_write_byte(struct arcxcnn *lp, u8 reg, u8 data)
> +{
> + s32 ret = -1;
> + int att;
> +
> + for (att = 0; att < s_retries; att++) {
> + ret = i2c_smbus_write_byte_data(lp->client, reg, data);
> + if (ret >= 0)
> + return 0;
> + }
> + return ret;
> +}
> +
> +static u8 arcxcnn_read_byte(struct arcxcnn *lp, u8 reg)
> +{
> + int val;
> + int att;
> +
> + for (att = 0; att < s_retries; att++) {
> + val = i2c_smbus_read_byte_data(lp->client, reg);
> + if (val >= 0)
> + return (u8)val;
> + }
> + return 0;
> +}
> +
> +static int arcxcnn_update_bit(struct arcxcnn *lp, u8 reg, u8 mask, u8 data)
> +{
> + int ret, att;
> + u8 tmp;
> +
> + for (att = 0, ret = -1; att < s_retries; att++) {
> + ret = i2c_smbus_read_byte_data(lp->client, reg);
> + if (ret >= 0)
> + break;
> + }
> + if (ret < 0) {
> + dev_err(lp->dev, "failed to read 0x%.2x\n", reg);
> + return ret;
> + }
> +
> + tmp = (u8)ret;
> + tmp &= ~mask;
> + tmp |= data & mask;
> +
> + return arcxcnn_write_byte(lp, reg, tmp);
> +}
> +
> +static int arcxcnn_set_brightness(struct arcxcnn *lp, u32 brightness)
> +{
> + int ret;
> + u8 val;
> +
> + val = (brightness & 0xF) << 4;
> + ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_LSB, val);
> + if (ret < 0)
> + return ret;
> + val = (brightness >> 4);
> + ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_MSB, val);
> + return ret;
> +}
> +
> +static int arcxcnn_bl_update_status(struct backlight_device *bl)
> +{
> + struct arcxcnn *lp = bl_get_data(bl);
> + u32 brightness = bl->props.brightness;
> +
> + if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
> + brightness = 0;
> +
> + /* set brightness */
> + if (lp->mode == PWM_BASED)
> + ; /* via pwm */
> + else if (lp->mode == REGISTER_BASED)
> + arcxcnn_set_brightness(lp, brightness);
> +
> + /* set power-on/off/save modes */
> + if (bl->props.power == 0)
> + /* take out of standby */
> + arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, 0);
> + else
> + /* 1-3 == power save, 4 = off
> + * place in low-power standby mode
> + */
> + arcxcnn_update_bit(lp, ARCXCNN_CMD,
> + ARCXCNN_CMD_STDBY, ARCXCNN_CMD_STDBY);
> + return 0;
> +}
> +
> +static const struct backlight_ops arcxcnn_bl_ops = {
> + .options = BL_CORE_SUSPENDRESUME,
> + .update_status = arcxcnn_bl_update_status,
> +};
> +
> +static int arcxcnn_backlight_register(struct arcxcnn *lp)
> +{
> + struct backlight_device *bl;
> + struct backlight_properties props;
> + struct arcxcnn_platform_data *pdata = lp->pdata;
> + const char *name = pdata->name ? : DEFAULT_BL_NAME;
> +
> + memset(&props, 0, sizeof(props));
> + props.type = BACKLIGHT_PLATFORM;
> + props.max_brightness = MAX_BRIGHTNESS;
> +
> + if (pdata->initial_brightness > props.max_brightness)
> + pdata->initial_brightness = props.max_brightness;
> +
> + props.brightness = pdata->initial_brightness;
> +
> + bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp,
> + &arcxcnn_bl_ops, &props);
> + if (IS_ERR(bl))
> + return PTR_ERR(bl);
> +
> + lp->bl = bl;
> +
> + return 0;
> +}
> +
> +static ssize_t arcxcnn_get_chip_id(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n", lp->chipname);
> +}
> +
> +static ssize_t arcxcnn_get_led_str(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> +
> + return scnprintf(buf, PAGE_SIZE, "%02X\n", lp->pdata->led_str);
> +}
> +
> +static ssize_t arcxcnn_set_led_str(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t len)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> + unsigned long ledstr;
> +
> + if (kstrtoul(buf, 0, &ledstr))
> + return 0;
> +
> + if (ledstr != lp->pdata->led_str) {
> + /* don't allow 0 for ledstr, use power to turn all off */
> + if (ledstr == 0)
> + return 0;
> + lp->pdata->led_str = ledstr & 0x3F;
> + arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
> + ARCXCNN_LEDEN_MASK, lp->pdata->led_str);
> + }
> + return len;
> +}
> +
> +static ssize_t arcxcnn_get_bl_ctl_mode(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> + char *strmode = NULL;
> +
> + if (lp->mode == PWM_BASED)
> + strmode = "pwm based";
> + else if (lp->mode == REGISTER_BASED)
> + strmode = "register based";
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n", strmode);
> +}
> +
> +static DEVICE_ATTR(chip_id, 0444, arcxcnn_get_chip_id, NULL);
> +static DEVICE_ATTR(led_str, 0664, arcxcnn_get_led_str, arcxcnn_set_led_str);
> +static DEVICE_ATTR(bl_ctl_mode, 0444, arcxcnn_get_bl_ctl_mode, NULL);
> +
> +static struct attribute *arcxcnn_attributes[] = {
> + &dev_attr_chip_id.attr,
> + &dev_attr_led_str.attr,
> + &dev_attr_bl_ctl_mode.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group arcxcnn_attr_group = {
> + .attrs = arcxcnn_attributes,
> +};
> +
> +#ifdef CONFIG_OF
> +static int arcxcnn_parse_dt(struct arcxcnn *lp)
> +{
> + struct device *dev = lp->dev;
> + struct device_node *node = dev->of_node;
> + u32 prog_val, num_entry, sources[6];
> + int ret;
> +
> + if (!node) {
> + dev_err(dev, "no platform data.\n");
> + return -EINVAL;
> + }
> + lp->pdata->led_config_0_set = false;
> + lp->pdata->led_config_1_set = false;
> + lp->pdata->dim_freq_set = false;
> + lp->pdata->comp_config_set = false;
> + lp->pdata->filter_config_set = false;
> + lp->pdata->trim_config_set = false;
> +
> + ret = of_property_read_string(node, "label", &lp->pdata->name);
> + if (ret < 0)
> + lp->pdata->name = NULL;
> +
> + ret = of_property_read_u32(node, "default-brightness", &prog_val);
> + if (ret < 0)
> + prog_val = s_ibright;
> + lp->pdata->initial_brightness = prog_val;
> + if (lp->pdata->initial_brightness > MAX_BRIGHTNESS)
> + lp->pdata->initial_brightness = MAX_BRIGHTNESS;
> +
> + ret = of_property_read_u32(node, "arcticsand,led-config-0", &prog_val);
> + if (ret == 0) {
> + lp->pdata->led_config_0 = (u8)prog_val;
> + lp->pdata->led_config_0_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,led-config-1", &prog_val);
> + if (ret == 0) {
> + lp->pdata->led_config_1 = (u8)prog_val;
> + lp->pdata->led_config_1_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,dim-freq", &prog_val);
> + if (ret == 0) {
> + lp->pdata->dim_freq = (u8)prog_val;
> + lp->pdata->dim_freq_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,comp-config", &prog_val);
> + if (ret == 0) {
> + lp->pdata->comp_config = (u8)prog_val;
> + lp->pdata->comp_config_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,filter-config", &prog_val);
> + if (ret == 0) {
> + lp->pdata->filter_config = (u8)prog_val;
> + lp->pdata->filter_config_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,trim-config", &prog_val);
> + if (ret == 0) {
> + lp->pdata->trim_config = (u8)prog_val;
> + lp->pdata->trim_config_set = true;
> + }
> + ret = of_property_count_u32_elems(node, "led-sources");
> + if (ret < 0)
> + lp->pdata->led_str = 0x3F;
> + else {
> + num_entry = ret;
> + if (num_entry > 6)
> + num_entry = 6;
> +
> + ret = of_property_read_u32_array(node, "led-sources", sources,
> + num_entry);
> + if (ret < 0) {
> + dev_err(dev, "led-sources node is invalid.\n");
> + return -EINVAL;
> + }
> +
> + lp->pdata->led_str = 0;
> + while (num_entry > 0)
> + lp->pdata->led_str |= (1 << sources[--num_entry]);
> + }
> + return 0;
> +}
> +#else
> +static int arcxcnn_parse_dt(struct arcxcnn *lp)
> +{
> + return -EINVAL;
> +}
> +#endif
> +
> +static int arcxcnn_probe(struct i2c_client *cl, const struct i2c_device_id *id)
> +{
> + struct arcxcnn *lp;
> + int ret;
> + u8 regval;
> + u16 chipid;
> +
> + if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
> + return -EIO;
> +
> + lp = devm_kzalloc(&cl->dev, sizeof(*lp), GFP_KERNEL);
> + if (!lp)
> + return -ENOMEM;
> +
> + lp->client = cl;
> + lp->dev = &cl->dev;
> + lp->chip_id = id->driver_data;
> + lp->pdata = dev_get_platdata(&cl->dev);
> +
> + if (!lp->pdata) {
> + lp->pdata = devm_kzalloc(lp->dev,
> + sizeof(*lp->pdata), GFP_KERNEL);
> + if (!lp->pdata)
> + return -ENOMEM;
> +
> + /* no platform data, parse the device-tree for info. if there
> + * is no device tree entry, we are being told we exist because
> + * user-land said so, so make up the info we need
> + */
> + ret = arcxcnn_parse_dt(lp);
> + if (ret < 0) {
> + /* no device tree, use defaults based on module params
> + */
> + lp->pdata->led_config_0_set = false;
> + lp->pdata->led_config_1_set = false;
> + lp->pdata->dim_freq_set = false;
> + lp->pdata->comp_config_set = false;
> + lp->pdata->filter_config_set = false;
> + lp->pdata->trim_config_set = false;
> +
> + lp->pdata->name = NULL;
> + lp->pdata->initial_brightness = s_ibright;
> + lp->pdata->led_str = s_iledstr;
> + }
> + }
> +
> + if (lp->pdata->dim_freq_set)
> + lp->mode = PWM_BASED;
> + else
> + lp->mode = REGISTER_BASED;
> +
> + i2c_set_clientdata(cl, lp);
> +
> + /* read device ID */
> + regval = arcxcnn_read_byte(lp, 0x1E);
> + chipid = regval;
> + chipid <<= 8;
> + regval = arcxcnn_read_byte(lp, 0x1F);
> + chipid |= regval;
> +
> + /* make sure it belongs to this driver
> + * TODO - handle specific ids
> + */
> + if (chipid != 0x02A5) {
> + #if 1
> + dev_info(&cl->dev, "Chip Id is %04X\n", chipid);
> + #else
> + dev_err(&cl->dev, "%04X is not ARC2C\n", chipid);
> + return -ENODEV;
> + #endif
> + }
> + /* reset the device */
> + arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
> +
> + /* set initial brightness */
> + arcxcnn_set_brightness(lp, lp->pdata->initial_brightness);
> +
> + /* if fadectrl set in DT, set the value directly, else leave default */
> + if (lp->pdata->led_config_0_set)
> + arcxcnn_write_byte(lp, ARCXCNN_FADECTRL,
> + lp->pdata->led_config_0);
> +
> + /* if iled config set in DT, set the value, else internal mode */
> + if (lp->pdata->led_config_1_set)
> + arcxcnn_write_byte(lp, ARCXCNN_ILED_CONFIG,
> + lp->pdata->led_config_1);
> + else
> + arcxcnn_write_byte(lp, ARCXCNN_ILED_CONFIG, 0x57);
> +
> + /* other misc DT settings */
> + if (lp->pdata->dim_freq_set)
> + arcxcnn_write_byte(lp, ARCXCNN_FADECTRL, lp->pdata->dim_freq);
> + if (lp->pdata->comp_config_set)
> + arcxcnn_write_byte(lp, ARCXCNN_COMP_CONFIG,
> + lp->pdata->comp_config);
> + if (lp->pdata->filter_config_set)
> + arcxcnn_write_byte(lp, ARCXCNN_FILT_CONFIG,
> + lp->pdata->filter_config);
> + if (lp->pdata->trim_config_set)
> + arcxcnn_write_byte(lp, ARCXCNN_IMAXTUNE,
> + lp->pdata->trim_config);
> +
> + /* set initial LED Strings */
> + arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
> + ARCXCNN_LEDEN_MASK, lp->pdata->led_str);
> +
> + snprintf(lp->chipname, sizeof(lp->chipname),
> + "%s-%04X", id->name, chipid);
> +
> + ret = arcxcnn_backlight_register(lp);
> + if (ret) {
> + dev_err(lp->dev,
> + "failed to register backlight. err: %d\n", ret);
> + return ret;
> + }
> +
> + ret = sysfs_create_group(&lp->dev->kobj, &arcxcnn_attr_group);
> + if (ret) {
> + dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret);
> + return ret;
> + }
> +
> + backlight_update_status(lp->bl);
> + return 0;
> +}
> +
> +static int arcxcnn_remove(struct i2c_client *cl)
> +{
> + struct arcxcnn *lp = i2c_get_clientdata(cl);
> +
> + if (!s_no_reset_on_remove) {
> + /* disable all strings */
> + arcxcnn_write_byte(lp, ARCXCNN_LEDEN, 0x00);
> + /* reset the device */
> + arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
> + }
> + lp->bl->props.brightness = 0;
> + backlight_update_status(lp->bl);
> + if (lp->supply)
> + regulator_disable(lp->supply);
> + sysfs_remove_group(&lp->dev->kobj, &arcxcnn_attr_group);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id arcxcnn_dt_ids[] = {
> + { .compatible = "arc,arc2c0608" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids);
> +
> +/* Note that the device/chip ID is not fixed in silicon so
> + * auto-probing of these devices on the bus is most likely
> + * not possible, use device tree to set i2c bus address
> + */
> +static const struct i2c_device_id arcxcnn_ids[] = {
> + {"arc2c0608", ARC2C0608},
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, arcxcnn_ids);
> +
> +static struct i2c_driver arcxcnn_driver = {
> + .driver = {
> + .name = "arcxcnn_bl",
> + .of_match_table = of_match_ptr(arcxcnn_dt_ids),
> + },
> + .probe = arcxcnn_probe,
> + .remove = arcxcnn_remove,
> + .id_table = arcxcnn_ids,
> +};
> +
> +module_i2c_driver(arcxcnn_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Brian Dodge <bdodge09-1ViLX0X+lBJBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_DESCRIPTION("ARCXCNN Backlight driver");
> diff --git a/include/linux/i2c/arcxcnn.h b/include/linux/i2c/arcxcnn.h
> new file mode 100644
> index 0000000..1c681dd
> --- /dev/null
> +++ b/include/linux/i2c/arcxcnn.h
> @@ -0,0 +1,67 @@
> +/*
> + * Backlight driver for ArcticSand ARC2C0608 Backlight Devices
> + *
> + * Copyright 2016 ArcticSand, Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#ifndef _ARCXCNN_H
> +#define _ARCXCNN_H
> +
> +enum arcxcnn_chip_id {
> + ARC2C0608
> +};
> +
> +enum arcxcnn_brightness_source {
> + ARCXCNN_PWM_ONLY,
> + ARCXCNN_I2C_ONLY = 2,
> +};
> +
> +#define ARCXCNN_MAX_PROGENTRIES 48 /* max a/v pairs for custom */
> +
> +/**
> + * struct arcxcnn_platform_data
> + * @name : Backlight driver name. If it is not defined, default name is set.
> + * @initial_brightness : initial value of backlight brightness
> + * @led_str : initial LED string enables, upper bit is global on/off
> + * @led_config_0 : fading speed (period between intensity steps)
> + * @led_config_1 : misc settings, see datasheet
> + * @dim_freq : pwm dimming frequency if in pwm mode
> + * @comp_config : misc config, see datasheet
> + * @filter_config: RC/PWM filter config, see datasheet
> + * @trim_config : full scale current trim, see datasheet
> + * @led_config_0_set : the value in led_config_0 is valid
> + * @led_config_1_set : the value in led_config_1 is valid
> + * @dim_freq_set : the value in dim_freq is valid
> + * @comp_config_set : the value in comp_config is valid
> + * @filter_config_set : the value in filter_config is valid
> + * @trim_config_set : the value in trim_config is valid
> + *
> + * the _set flags are used to indicate that the value was explicitly set
> + * in the device tree or platform data. settings not set are left as default
> + * power-on default values of the chip except for led_str and led_config_1
> + * which are set by the driver (led_str is specified indirectly in the
> + * device tree via "led-sources")
> + */
> +struct arcxcnn_platform_data {
> + const char *name;
> + u16 initial_brightness;
> + u8 led_str;
> +
> + u8 led_config_0;
> + u8 led_config_1;
> + u8 dim_freq;
> + u8 comp_config;
> + u8 filter_config;
> + u8 trim_config;
> +
> + bool led_config_0_set;
> + bool led_config_1_set;
> + bool dim_freq_set;
> + bool comp_config_set;
> + bool filter_config_set;
> + bool trim_config_set;
> +};
> +
> +#endif
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH 1/2] backlight: arcxcnn: add support for ArticSand devices
From: Lee Jones @ 2016-11-09 15:53 UTC (permalink / raw)
To: Olimpiu Dejeu
Cc: robh-DgEjT+Ai2ygdnm+yROfE0A, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, jg1.han-Sze3O3UU22JBDgjK7y7TUQ
In-Reply-To: <1477513778-31297-1-git-send-email-olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org>
Jingoo?
On Wed, 26 Oct 2016, Olimpiu Dejeu wrote:
> Resubmition of arcxcnn backliught driver adding devicetree entries
> for all registers
>
> Signed-off-by: Olimpiu Dejeu <olimpiu@arcticsand.com>
>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 1 +
> drivers/video/backlight/arcxcnn_bl.c | 541 +++++++++++++++++++++++++++++++++++
> include/linux/i2c/arcxcnn.h | 67 +++++
> 4 files changed, 616 insertions(+)
> create mode 100644 drivers/video/backlight/arcxcnn_bl.c
> create mode 100644 include/linux/i2c/arcxcnn.h
>
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 5ffa4b4..4e1d2ad 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -460,6 +460,13 @@ config BACKLIGHT_BD6107
> help
> If you have a Rohm BD6107 say Y to enable the backlight driver.
>
> +config BACKLIGHT_ARCXCNN
> + tristate "Backlight driver for the Arctic Sands ARCxCnnnn family"
> + depends on I2C
> + help
> + If you have an ARCxCnnnn family backlight say Y to enable
> + the backlight driver.
> +
> endif # BACKLIGHT_CLASS_DEVICE
>
> endif # BACKLIGHT_LCD_SUPPORT
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index 16ec534..8905129 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -55,3 +55,4 @@ obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o
> obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
> obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
> obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
> +obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o
> diff --git a/drivers/video/backlight/arcxcnn_bl.c b/drivers/video/backlight/arcxcnn_bl.c
> new file mode 100644
> index 0000000..1dad680
> --- /dev/null
> +++ b/drivers/video/backlight/arcxcnn_bl.c
> @@ -0,0 +1,541 @@
> +/*
> + * Backlight driver for ArcticSand ARC_X_C_0N_0N Devices
> + *
> + * Copyright 2016 ArcticSand, Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/backlight.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/pwm.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include "linux/i2c/arcxcnn.h"
> +
> +#define ARCXCNN_CMD (0x00) /* Command Register */
> +#define ARCXCNN_CMD_STDBY (0x80) /* I2C Standby */
> +#define ARCXCNN_CMD_RESET (0x40) /* Reset */
> +#define ARCXCNN_CMD_BOOST (0x10) /* Boost */
> +#define ARCXCNN_CMD_OVP_MASK (0x0C) /* --- Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_XXV (0x0C) /* <rsvrd> Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_20V (0x08) /* 20v Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_24V (0x04) /* 24v Over Voltage Threshold */
> +#define ARCXCNN_CMD_OVP_31V (0x00) /* 31.4v Over Voltage Threshold */
> +#define ARCXCNN_CMD_EXT_COMP (0x01) /* part (0) or full (1) external comp */
> +
> +#define ARCXCNN_CONFIG (0x01) /* Configuration */
> +#define ARCXCNN_STATUS1 (0x02) /* Status 1 */
> +#define ARCXCNN_STATUS2 (0x03) /* Status 2 */
> +#define ARCXCNN_FADECTRL (0x04) /* Fading Control */
> +#define ARCXCNN_ILED_CONFIG (0x05) /* ILED Configuration */
> +
> +#define ARCXCNN_LEDEN (0x06) /* LED Enable Register */
> +#define ARCXCNN_LEDEN_ISETEXT (0x80) /* Full-scale current set externally */
> +#define ARCXCNN_LEDEN_MASK (0x3F) /* LED string enables */
> +#define ARCXCNN_LEDEN_LED1 (0x01)
> +#define ARCXCNN_LEDEN_LED2 (0x02)
> +#define ARCXCNN_LEDEN_LED3 (0x04)
> +#define ARCXCNN_LEDEN_LED4 (0x08)
> +#define ARCXCNN_LEDEN_LED5 (0x10)
> +#define ARCXCNN_LEDEN_LED6 (0x20)
> +
> +#define ARCXCNN_WLED_ISET_LSB (0x07) /* LED ISET LSB (in upper nibble) */
> +#define ARCXCNN_WLED_ISET_MSB (0x08) /* LED ISET MSB (8 bits) */
> +
> +#define ARCXCNN_DIMFREQ (0x09)
> +#define ARCXCNN_COMP_CONFIG (0x0A)
> +#define ARCXCNN_FILT_CONFIG (0x0B)
> +#define ARCXCNN_IMAXTUNE (0x0C)
> +
> +#define DEFAULT_BL_NAME "arctic_bl"
> +#define MAX_BRIGHTNESS 4095
> +
> +static int s_no_reset_on_remove;
> +module_param_named(noreset, s_no_reset_on_remove, int, 0644);
> +MODULE_PARM_DESC(noreset, "No reset on module removal");
> +
> +static int s_ibright = 60;
> +module_param_named(ibright, s_ibright, int, 0644);
> +MODULE_PARM_DESC(ibright, "Initial brightness (when no plat data)");
> +
> +static int s_iledstr = 0x3F;
> +module_param_named(iledstr, s_iledstr, int, 0644);
> +MODULE_PARM_DESC(iledstr, "Initial LED String (when no plat data)");
> +
> +static int s_retries = 2; /* 1 = only one try */
> +module_param_named(retries, s_retries, int, 0644);
> +MODULE_PARM_DESC(retries, "I2C retries attempted");
> +
> +enum arcxcnn_brightness_ctrl_mode {
> + PWM_BASED = 1,
> + REGISTER_BASED,
> +};
> +
> +struct arcxcnn;
> +
> +struct arcxcnn {
> + char chipname[64];
> + enum arcxcnn_chip_id chip_id;
> + enum arcxcnn_brightness_ctrl_mode mode;
> + struct i2c_client *client;
> + struct backlight_device *bl;
> + struct device *dev;
> + struct arcxcnn_platform_data *pdata;
> + struct pwm_device *pwm;
> + struct regulator *supply; /* regulator for VDD input */
> +};
> +
> +static int arcxcnn_write_byte(struct arcxcnn *lp, u8 reg, u8 data)
> +{
> + s32 ret = -1;
> + int att;
> +
> + for (att = 0; att < s_retries; att++) {
> + ret = i2c_smbus_write_byte_data(lp->client, reg, data);
> + if (ret >= 0)
> + return 0;
> + }
> + return ret;
> +}
> +
> +static u8 arcxcnn_read_byte(struct arcxcnn *lp, u8 reg)
> +{
> + int val;
> + int att;
> +
> + for (att = 0; att < s_retries; att++) {
> + val = i2c_smbus_read_byte_data(lp->client, reg);
> + if (val >= 0)
> + return (u8)val;
> + }
> + return 0;
> +}
> +
> +static int arcxcnn_update_bit(struct arcxcnn *lp, u8 reg, u8 mask, u8 data)
> +{
> + int ret, att;
> + u8 tmp;
> +
> + for (att = 0, ret = -1; att < s_retries; att++) {
> + ret = i2c_smbus_read_byte_data(lp->client, reg);
> + if (ret >= 0)
> + break;
> + }
> + if (ret < 0) {
> + dev_err(lp->dev, "failed to read 0x%.2x\n", reg);
> + return ret;
> + }
> +
> + tmp = (u8)ret;
> + tmp &= ~mask;
> + tmp |= data & mask;
> +
> + return arcxcnn_write_byte(lp, reg, tmp);
> +}
> +
> +static int arcxcnn_set_brightness(struct arcxcnn *lp, u32 brightness)
> +{
> + int ret;
> + u8 val;
> +
> + val = (brightness & 0xF) << 4;
> + ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_LSB, val);
> + if (ret < 0)
> + return ret;
> + val = (brightness >> 4);
> + ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_MSB, val);
> + return ret;
> +}
> +
> +static int arcxcnn_bl_update_status(struct backlight_device *bl)
> +{
> + struct arcxcnn *lp = bl_get_data(bl);
> + u32 brightness = bl->props.brightness;
> +
> + if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
> + brightness = 0;
> +
> + /* set brightness */
> + if (lp->mode = PWM_BASED)
> + ; /* via pwm */
> + else if (lp->mode = REGISTER_BASED)
> + arcxcnn_set_brightness(lp, brightness);
> +
> + /* set power-on/off/save modes */
> + if (bl->props.power = 0)
> + /* take out of standby */
> + arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, 0);
> + else
> + /* 1-3 = power save, 4 = off
> + * place in low-power standby mode
> + */
> + arcxcnn_update_bit(lp, ARCXCNN_CMD,
> + ARCXCNN_CMD_STDBY, ARCXCNN_CMD_STDBY);
> + return 0;
> +}
> +
> +static const struct backlight_ops arcxcnn_bl_ops = {
> + .options = BL_CORE_SUSPENDRESUME,
> + .update_status = arcxcnn_bl_update_status,
> +};
> +
> +static int arcxcnn_backlight_register(struct arcxcnn *lp)
> +{
> + struct backlight_device *bl;
> + struct backlight_properties props;
> + struct arcxcnn_platform_data *pdata = lp->pdata;
> + const char *name = pdata->name ? : DEFAULT_BL_NAME;
> +
> + memset(&props, 0, sizeof(props));
> + props.type = BACKLIGHT_PLATFORM;
> + props.max_brightness = MAX_BRIGHTNESS;
> +
> + if (pdata->initial_brightness > props.max_brightness)
> + pdata->initial_brightness = props.max_brightness;
> +
> + props.brightness = pdata->initial_brightness;
> +
> + bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp,
> + &arcxcnn_bl_ops, &props);
> + if (IS_ERR(bl))
> + return PTR_ERR(bl);
> +
> + lp->bl = bl;
> +
> + return 0;
> +}
> +
> +static ssize_t arcxcnn_get_chip_id(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n", lp->chipname);
> +}
> +
> +static ssize_t arcxcnn_get_led_str(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> +
> + return scnprintf(buf, PAGE_SIZE, "%02X\n", lp->pdata->led_str);
> +}
> +
> +static ssize_t arcxcnn_set_led_str(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t len)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> + unsigned long ledstr;
> +
> + if (kstrtoul(buf, 0, &ledstr))
> + return 0;
> +
> + if (ledstr != lp->pdata->led_str) {
> + /* don't allow 0 for ledstr, use power to turn all off */
> + if (ledstr = 0)
> + return 0;
> + lp->pdata->led_str = ledstr & 0x3F;
> + arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
> + ARCXCNN_LEDEN_MASK, lp->pdata->led_str);
> + }
> + return len;
> +}
> +
> +static ssize_t arcxcnn_get_bl_ctl_mode(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct arcxcnn *lp = dev_get_drvdata(dev);
> + char *strmode = NULL;
> +
> + if (lp->mode = PWM_BASED)
> + strmode = "pwm based";
> + else if (lp->mode = REGISTER_BASED)
> + strmode = "register based";
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n", strmode);
> +}
> +
> +static DEVICE_ATTR(chip_id, 0444, arcxcnn_get_chip_id, NULL);
> +static DEVICE_ATTR(led_str, 0664, arcxcnn_get_led_str, arcxcnn_set_led_str);
> +static DEVICE_ATTR(bl_ctl_mode, 0444, arcxcnn_get_bl_ctl_mode, NULL);
> +
> +static struct attribute *arcxcnn_attributes[] = {
> + &dev_attr_chip_id.attr,
> + &dev_attr_led_str.attr,
> + &dev_attr_bl_ctl_mode.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group arcxcnn_attr_group = {
> + .attrs = arcxcnn_attributes,
> +};
> +
> +#ifdef CONFIG_OF
> +static int arcxcnn_parse_dt(struct arcxcnn *lp)
> +{
> + struct device *dev = lp->dev;
> + struct device_node *node = dev->of_node;
> + u32 prog_val, num_entry, sources[6];
> + int ret;
> +
> + if (!node) {
> + dev_err(dev, "no platform data.\n");
> + return -EINVAL;
> + }
> + lp->pdata->led_config_0_set = false;
> + lp->pdata->led_config_1_set = false;
> + lp->pdata->dim_freq_set = false;
> + lp->pdata->comp_config_set = false;
> + lp->pdata->filter_config_set = false;
> + lp->pdata->trim_config_set = false;
> +
> + ret = of_property_read_string(node, "label", &lp->pdata->name);
> + if (ret < 0)
> + lp->pdata->name = NULL;
> +
> + ret = of_property_read_u32(node, "default-brightness", &prog_val);
> + if (ret < 0)
> + prog_val = s_ibright;
> + lp->pdata->initial_brightness = prog_val;
> + if (lp->pdata->initial_brightness > MAX_BRIGHTNESS)
> + lp->pdata->initial_brightness = MAX_BRIGHTNESS;
> +
> + ret = of_property_read_u32(node, "arcticsand,led-config-0", &prog_val);
> + if (ret = 0) {
> + lp->pdata->led_config_0 = (u8)prog_val;
> + lp->pdata->led_config_0_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,led-config-1", &prog_val);
> + if (ret = 0) {
> + lp->pdata->led_config_1 = (u8)prog_val;
> + lp->pdata->led_config_1_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,dim-freq", &prog_val);
> + if (ret = 0) {
> + lp->pdata->dim_freq = (u8)prog_val;
> + lp->pdata->dim_freq_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,comp-config", &prog_val);
> + if (ret = 0) {
> + lp->pdata->comp_config = (u8)prog_val;
> + lp->pdata->comp_config_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,filter-config", &prog_val);
> + if (ret = 0) {
> + lp->pdata->filter_config = (u8)prog_val;
> + lp->pdata->filter_config_set = true;
> + }
> + ret = of_property_read_u32(node, "arcticsand,trim-config", &prog_val);
> + if (ret = 0) {
> + lp->pdata->trim_config = (u8)prog_val;
> + lp->pdata->trim_config_set = true;
> + }
> + ret = of_property_count_u32_elems(node, "led-sources");
> + if (ret < 0)
> + lp->pdata->led_str = 0x3F;
> + else {
> + num_entry = ret;
> + if (num_entry > 6)
> + num_entry = 6;
> +
> + ret = of_property_read_u32_array(node, "led-sources", sources,
> + num_entry);
> + if (ret < 0) {
> + dev_err(dev, "led-sources node is invalid.\n");
> + return -EINVAL;
> + }
> +
> + lp->pdata->led_str = 0;
> + while (num_entry > 0)
> + lp->pdata->led_str |= (1 << sources[--num_entry]);
> + }
> + return 0;
> +}
> +#else
> +static int arcxcnn_parse_dt(struct arcxcnn *lp)
> +{
> + return -EINVAL;
> +}
> +#endif
> +
> +static int arcxcnn_probe(struct i2c_client *cl, const struct i2c_device_id *id)
> +{
> + struct arcxcnn *lp;
> + int ret;
> + u8 regval;
> + u16 chipid;
> +
> + if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
> + return -EIO;
> +
> + lp = devm_kzalloc(&cl->dev, sizeof(*lp), GFP_KERNEL);
> + if (!lp)
> + return -ENOMEM;
> +
> + lp->client = cl;
> + lp->dev = &cl->dev;
> + lp->chip_id = id->driver_data;
> + lp->pdata = dev_get_platdata(&cl->dev);
> +
> + if (!lp->pdata) {
> + lp->pdata = devm_kzalloc(lp->dev,
> + sizeof(*lp->pdata), GFP_KERNEL);
> + if (!lp->pdata)
> + return -ENOMEM;
> +
> + /* no platform data, parse the device-tree for info. if there
> + * is no device tree entry, we are being told we exist because
> + * user-land said so, so make up the info we need
> + */
> + ret = arcxcnn_parse_dt(lp);
> + if (ret < 0) {
> + /* no device tree, use defaults based on module params
> + */
> + lp->pdata->led_config_0_set = false;
> + lp->pdata->led_config_1_set = false;
> + lp->pdata->dim_freq_set = false;
> + lp->pdata->comp_config_set = false;
> + lp->pdata->filter_config_set = false;
> + lp->pdata->trim_config_set = false;
> +
> + lp->pdata->name = NULL;
> + lp->pdata->initial_brightness = s_ibright;
> + lp->pdata->led_str = s_iledstr;
> + }
> + }
> +
> + if (lp->pdata->dim_freq_set)
> + lp->mode = PWM_BASED;
> + else
> + lp->mode = REGISTER_BASED;
> +
> + i2c_set_clientdata(cl, lp);
> +
> + /* read device ID */
> + regval = arcxcnn_read_byte(lp, 0x1E);
> + chipid = regval;
> + chipid <<= 8;
> + regval = arcxcnn_read_byte(lp, 0x1F);
> + chipid |= regval;
> +
> + /* make sure it belongs to this driver
> + * TODO - handle specific ids
> + */
> + if (chipid != 0x02A5) {
> + #if 1
> + dev_info(&cl->dev, "Chip Id is %04X\n", chipid);
> + #else
> + dev_err(&cl->dev, "%04X is not ARC2C\n", chipid);
> + return -ENODEV;
> + #endif
> + }
> + /* reset the device */
> + arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
> +
> + /* set initial brightness */
> + arcxcnn_set_brightness(lp, lp->pdata->initial_brightness);
> +
> + /* if fadectrl set in DT, set the value directly, else leave default */
> + if (lp->pdata->led_config_0_set)
> + arcxcnn_write_byte(lp, ARCXCNN_FADECTRL,
> + lp->pdata->led_config_0);
> +
> + /* if iled config set in DT, set the value, else internal mode */
> + if (lp->pdata->led_config_1_set)
> + arcxcnn_write_byte(lp, ARCXCNN_ILED_CONFIG,
> + lp->pdata->led_config_1);
> + else
> + arcxcnn_write_byte(lp, ARCXCNN_ILED_CONFIG, 0x57);
> +
> + /* other misc DT settings */
> + if (lp->pdata->dim_freq_set)
> + arcxcnn_write_byte(lp, ARCXCNN_FADECTRL, lp->pdata->dim_freq);
> + if (lp->pdata->comp_config_set)
> + arcxcnn_write_byte(lp, ARCXCNN_COMP_CONFIG,
> + lp->pdata->comp_config);
> + if (lp->pdata->filter_config_set)
> + arcxcnn_write_byte(lp, ARCXCNN_FILT_CONFIG,
> + lp->pdata->filter_config);
> + if (lp->pdata->trim_config_set)
> + arcxcnn_write_byte(lp, ARCXCNN_IMAXTUNE,
> + lp->pdata->trim_config);
> +
> + /* set initial LED Strings */
> + arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
> + ARCXCNN_LEDEN_MASK, lp->pdata->led_str);
> +
> + snprintf(lp->chipname, sizeof(lp->chipname),
> + "%s-%04X", id->name, chipid);
> +
> + ret = arcxcnn_backlight_register(lp);
> + if (ret) {
> + dev_err(lp->dev,
> + "failed to register backlight. err: %d\n", ret);
> + return ret;
> + }
> +
> + ret = sysfs_create_group(&lp->dev->kobj, &arcxcnn_attr_group);
> + if (ret) {
> + dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret);
> + return ret;
> + }
> +
> + backlight_update_status(lp->bl);
> + return 0;
> +}
> +
> +static int arcxcnn_remove(struct i2c_client *cl)
> +{
> + struct arcxcnn *lp = i2c_get_clientdata(cl);
> +
> + if (!s_no_reset_on_remove) {
> + /* disable all strings */
> + arcxcnn_write_byte(lp, ARCXCNN_LEDEN, 0x00);
> + /* reset the device */
> + arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
> + }
> + lp->bl->props.brightness = 0;
> + backlight_update_status(lp->bl);
> + if (lp->supply)
> + regulator_disable(lp->supply);
> + sysfs_remove_group(&lp->dev->kobj, &arcxcnn_attr_group);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id arcxcnn_dt_ids[] = {
> + { .compatible = "arc,arc2c0608" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids);
> +
> +/* Note that the device/chip ID is not fixed in silicon so
> + * auto-probing of these devices on the bus is most likely
> + * not possible, use device tree to set i2c bus address
> + */
> +static const struct i2c_device_id arcxcnn_ids[] = {
> + {"arc2c0608", ARC2C0608},
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, arcxcnn_ids);
> +
> +static struct i2c_driver arcxcnn_driver = {
> + .driver = {
> + .name = "arcxcnn_bl",
> + .of_match_table = of_match_ptr(arcxcnn_dt_ids),
> + },
> + .probe = arcxcnn_probe,
> + .remove = arcxcnn_remove,
> + .id_table = arcxcnn_ids,
> +};
> +
> +module_i2c_driver(arcxcnn_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Brian Dodge <bdodge09@outlook.com>");
> +MODULE_DESCRIPTION("ARCXCNN Backlight driver");
> diff --git a/include/linux/i2c/arcxcnn.h b/include/linux/i2c/arcxcnn.h
> new file mode 100644
> index 0000000..1c681dd
> --- /dev/null
> +++ b/include/linux/i2c/arcxcnn.h
> @@ -0,0 +1,67 @@
> +/*
> + * Backlight driver for ArcticSand ARC2C0608 Backlight Devices
> + *
> + * Copyright 2016 ArcticSand, Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#ifndef _ARCXCNN_H
> +#define _ARCXCNN_H
> +
> +enum arcxcnn_chip_id {
> + ARC2C0608
> +};
> +
> +enum arcxcnn_brightness_source {
> + ARCXCNN_PWM_ONLY,
> + ARCXCNN_I2C_ONLY = 2,
> +};
> +
> +#define ARCXCNN_MAX_PROGENTRIES 48 /* max a/v pairs for custom */
> +
> +/**
> + * struct arcxcnn_platform_data
> + * @name : Backlight driver name. If it is not defined, default name is set.
> + * @initial_brightness : initial value of backlight brightness
> + * @led_str : initial LED string enables, upper bit is global on/off
> + * @led_config_0 : fading speed (period between intensity steps)
> + * @led_config_1 : misc settings, see datasheet
> + * @dim_freq : pwm dimming frequency if in pwm mode
> + * @comp_config : misc config, see datasheet
> + * @filter_config: RC/PWM filter config, see datasheet
> + * @trim_config : full scale current trim, see datasheet
> + * @led_config_0_set : the value in led_config_0 is valid
> + * @led_config_1_set : the value in led_config_1 is valid
> + * @dim_freq_set : the value in dim_freq is valid
> + * @comp_config_set : the value in comp_config is valid
> + * @filter_config_set : the value in filter_config is valid
> + * @trim_config_set : the value in trim_config is valid
> + *
> + * the _set flags are used to indicate that the value was explicitly set
> + * in the device tree or platform data. settings not set are left as default
> + * power-on default values of the chip except for led_str and led_config_1
> + * which are set by the driver (led_str is specified indirectly in the
> + * device tree via "led-sources")
> + */
> +struct arcxcnn_platform_data {
> + const char *name;
> + u16 initial_brightness;
> + u8 led_str;
> +
> + u8 led_config_0;
> + u8 led_config_1;
> + u8 dim_freq;
> + u8 comp_config;
> + u8 filter_config;
> + u8 trim_config;
> +
> + bool led_config_0_set;
> + bool led_config_1_set;
> + bool dim_freq_set;
> + bool comp_config_set;
> + bool filter_config_set;
> + bool trim_config_set;
> +};
> +
> +#endif
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.