Netdev List
 help / color / mirror / Atom feed
* [iproute2] bridge: Fix garbled json output seen if a vlan filter is specified
From: Anuradha Karuppiah @ 2016-10-07 16:40 UTC (permalink / raw)
  To: stephen, netdev; +Cc: roopa, anuradhak

From: anuradhak <anuradhak@cumulusnetworks.com>

json objects were started but not completed if the fdb vlan did not
match the specified filter vlan.

Sample output:
$ bridge -j fdb show vlan 111
[{
        "mac": "44:38:39:00:69:88",
        "dev": "br0",
        "vlan": 111,
        "master": "br0",
        "state": "permanent"
    }
]
$ bridge -j fdb show vlan 100
[]
$

Signed-off-by: Anuradha Karuppiah <anuradhak@cumulusnetworks.com>
---
 bridge/fdb.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/bridge/fdb.c b/bridge/fdb.c
index c6e0379..90f4b15 100644
--- a/bridge/fdb.c
+++ b/bridge/fdb.c
@@ -100,11 +100,6 @@ int print_fdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
 	if (filter_index && filter_index != r->ndm_ifindex)
 		return 0;
 
-	if (jw_global) {
-		jsonw_pretty(jw_global, 1);
-		jsonw_start_object(jw_global);
-	}
-
 	parse_rtattr(tb, NDA_MAX, NDA_RTA(r),
 		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
 
@@ -114,6 +109,11 @@ int print_fdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
 	if (filter_vlan && filter_vlan != vid)
 		return 0;
 
+	if (jw_global) {
+		jsonw_pretty(jw_global, 1);
+		jsonw_start_object(jw_global);
+	}
+
 	if (n->nlmsg_type == RTM_DELNEIGH) {
 		if (jw_global)
 			jsonw_string_field(jw_global, "opCode", "deleted");
-- 
2.1.4

^ permalink raw reply related

* Re: [PATCH] net: Reset skb to network header in neigh_hh_output
From: Sergei Shtylyov @ 2016-10-07 16:38 UTC (permalink / raw)
  To: Abdelrhman Ahmed, davem; +Cc: netdev, linux-kernel
In-Reply-To: <572cddd1-6a03-4c83-1e0e-55be37273396@cogentembedded.com>

On 10/07/2016 07:27 PM, Sergei Shtylyov wrote:

>> When hardware header is added without using cached one, neigh_resolve_output
>> and neigh_connected_output reset skb to network header before adding it.
>> When cached one is used, neigh_hh_output does not reset the skb to network
>> header.
>>
>> The fix is to reset skb to network header before adding cached hardware header
>> to keep the behavior consistent in all cases.
>>
>> Signed-off-by: Abdelrhman Ahmed <ab@abahmed.com>
>> ---
>>  include/net/neighbour.h | 4 +++-
>>  1 file changed, 3 insertions(+), 1 deletion(-)
>>
>> diff --git a/include/net/neighbour.h b/include/net/neighbour.h
>> index 8b68384..4d89fc2 100644
>> --- a/include/net/neighbour.h
>> +++ b/include/net/neighbour.h
>> @@ -424,7 +424,7 @@ static inline struct neighbour * neigh_clone(struct
>> neighbour *neigh)
>>  static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff
>> *skb)
>>  {
>>         unsigned long now = jiffies;
>> -
>> +
>
>    Unraleted white-space change.

    And I thought I fixed this word... it's "unrelated" of/c. :-)

>
>>         if (neigh->used != now)
>>                 neigh->used = now;
>>         if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
> [...]

MBR, Sergei

^ permalink raw reply

* Re: [PATCH] net: Reset skb to network header in neigh_hh_output
From: Sergei Shtylyov @ 2016-10-07 16:27 UTC (permalink / raw)
  To: Abdelrhman Ahmed, davem; +Cc: netdev, linux-kernel
In-Reply-To: <1579f7f0f4f.114457ec828613.4349884782265574217@abahmed.com>

Hello.

On 10/07/2016 05:14 PM, Abdelrhman Ahmed wrote:

> When hardware header is added without using cached one, neigh_resolve_output
> and neigh_connected_output reset skb to network header before adding it.
> When cached one is used, neigh_hh_output does not reset the skb to network
> header.
>
> The fix is to reset skb to network header before adding cached hardware header
> to keep the behavior consistent in all cases.
>
> Signed-off-by: Abdelrhman Ahmed <ab@abahmed.com>
> ---
>  include/net/neighbour.h | 4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/include/net/neighbour.h b/include/net/neighbour.h
> index 8b68384..4d89fc2 100644
> --- a/include/net/neighbour.h
> +++ b/include/net/neighbour.h
> @@ -424,7 +424,7 @@ static inline struct neighbour * neigh_clone(struct neighbour *neigh)
>  static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
>  {
>         unsigned long now = jiffies;
> -
> +

    Unraleted white-space change.

>         if (neigh->used != now)
>                 neigh->used = now;
>         if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
[...]

MBR, Sergei

^ permalink raw reply

* Re: [PATCH] net: macb: NULL out phydev after removing mdio bus
From: Nicolas Ferre @ 2016-10-07 16:22 UTC (permalink / raw)
  To: Xander Huff; +Cc: netdev, Nathan Sullivan, linux-kernel, Brad Mouring
In-Reply-To: <1475853202-19903-1-git-send-email-xander.huff@ni.com>

Le 07/10/2016 à 17:13, Xander Huff a écrit :
> From: Nathan Sullivan <nathan.sullivan@ni.com>
> 
> To ensure the dev->phydev pointer is not used after becoming invalid in
> mdiobus_unregister, set it to NULL. This happens when removing the macb
> driver without first taking its interface down, since unregister_netdev
> will end up calling macb_close.
> 
> Signed-off-by: Xander Huff <xander.huff@ni.com>
> Signed-off-by: Nathan Sullivan <nathan.sullivan@ni.com>
> Signed-off-by: Brad Mouring <brad.mouring@ni.com>

Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

> ---
>  drivers/net/ethernet/cadence/macb.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/net/ethernet/cadence/macb.c b/drivers/net/ethernet/cadence/macb.c
> index 63144bb..b32444a 100644
> --- a/drivers/net/ethernet/cadence/macb.c
> +++ b/drivers/net/ethernet/cadence/macb.c
> @@ -3117,6 +3117,7 @@ static int macb_remove(struct platform_device *pdev)
>  		if (dev->phydev)
>  			phy_disconnect(dev->phydev);
>  		mdiobus_unregister(bp->mii_bus);
> +		dev->phydev = NULL;
>  		mdiobus_free(bp->mii_bus);
>  
>  		/* Shutdown the PHY if there is a GPIO reset */
> 


-- 
Nicolas Ferre

^ permalink raw reply

* [PATCH net-next v3 5/6] openvswitch: remove unreachable code in vlan parsing
From: Jiri Benc @ 2016-10-07 16:07 UTC (permalink / raw)
  To: netdev; +Cc: pravin shelar, Eric Garver
In-Reply-To: <cover.1475855896.git.jbenc@redhat.com>

Now when the first vlan tag is always in skb->vlan_tci, drop code that
assumed it might not be the case.

This patch also removes the wrong likely() statement around
skb_vlan_tag_present introduced by 018c1dda5ff1 ("openvswitch: 802.1AD Flow
handling, actions, vlan parsing, netlink attributes"). This code is called
whenever flow key is being extracted from the packet, the packet may be as
likely vlan tagged as not.

Signed-off-by: Jiri Benc <jbenc@redhat.com>
---
v3: unchanged
---
 net/openvswitch/flow.c | 28 ++++++++--------------------
 1 file changed, 8 insertions(+), 20 deletions(-)

diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c
index 14159ac19850..45e384167053 100644
--- a/net/openvswitch/flow.c
+++ b/net/openvswitch/flow.c
@@ -308,9 +308,7 @@ static bool icmp6hdr_ok(struct sk_buff *skb)
 
 /**
  * Parse vlan tag from vlan header.
- * Returns ERROR on memory error.
- * Returns 0 if it encounters a non-vlan or incomplete packet.
- * Returns 1 after successfully parsing vlan tag.
+ * Returns ERROR on memory error, 0 otherwise.
  */
 static int parse_vlan_tag(struct sk_buff *skb, struct vlan_head *key_vh)
 {
@@ -331,34 +329,24 @@ static int parse_vlan_tag(struct sk_buff *skb, struct vlan_head *key_vh)
 	key_vh->tpid = vh->tpid;
 
 	__skb_pull(skb, sizeof(struct vlan_head));
-	return 1;
+	return 0;
 }
 
 static int parse_vlan(struct sk_buff *skb, struct sw_flow_key *key)
 {
-	int res;
-
 	key->eth.vlan.tci = 0;
 	key->eth.vlan.tpid = 0;
 	key->eth.cvlan.tci = 0;
 	key->eth.cvlan.tpid = 0;
 
-	if (likely(skb_vlan_tag_present(skb))) {
-		key->eth.vlan.tci = htons(skb->vlan_tci);
-		key->eth.vlan.tpid = skb->vlan_proto;
-	} else {
-		/* Parse outer vlan tag in the non-accelerated case. */
-		res = parse_vlan_tag(skb, &key->eth.vlan);
-		if (res <= 0)
-			return res;
-	}
+	if (!skb_vlan_tag_present(skb))
+		return 0;
 
-	/* Parse inner vlan tag. */
-	res = parse_vlan_tag(skb, &key->eth.cvlan);
-	if (res <= 0)
-		return res;
+	key->eth.vlan.tci = htons(skb->vlan_tci);
+	key->eth.vlan.tpid = skb->vlan_proto;
 
-	return 0;
+	/* Parse inner vlan tag. */
+	return parse_vlan_tag(skb, &key->eth.cvlan);
 }
 
 static __be16 parse_ethertype(struct sk_buff *skb)
-- 
1.8.3.1

^ permalink raw reply related

* [PATCH net-next v3 6/6] openvswitch: fix vlan subtraction from packet length
From: Jiri Benc @ 2016-10-07 16:07 UTC (permalink / raw)
  To: netdev; +Cc: pravin shelar, Eric Garver
In-Reply-To: <cover.1475855896.git.jbenc@redhat.com>

When the packet has its vlan tag in skb->vlan_tci, the length of the VLAN
header is not counted in skb->len. It doesn't make sense to subtract it.

In addition, to honor the comment below the code, the VLAN header length
should not be subtracted if there's a vlan tag in skb->vlan_tci. This leads
to the code simply subtracting the Ethernet header length.

Signed-off-by: Jiri Benc <jbenc@redhat.com>
---
v3: unchanged
---
 net/openvswitch/vport.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c
index 8aefcb20cc58..45370dd6a685 100644
--- a/net/openvswitch/vport.c
+++ b/net/openvswitch/vport.c
@@ -481,17 +481,13 @@ EXPORT_SYMBOL_GPL(ovs_vport_deferred_free);
 
 static unsigned int packet_length(const struct sk_buff *skb)
 {
-	unsigned int length = skb->len - ETH_HLEN;
-
-	if (skb_vlan_tagged(skb))
-		length -= VLAN_HLEN;
-
 	/* Don't subtract for multiple VLAN tags. Most (all?) drivers allow
 	 * (ETH_LEN + VLAN_HLEN) in addition to the mtu value, but almost none
 	 * account for 802.1ad. e.g. is_skb_forwardable().
+	 * Note that the first VLAN tag is always in skb->vlan_tci, thus not
+	 * accounted for in skb->len.
 	 */
-
-	return length;
+	return skb->len - ETH_HLEN;
 }
 
 void ovs_vport_send(struct vport *vport, struct sk_buff *skb)
-- 
1.8.3.1

^ permalink raw reply related

* [PATCH net-next v3 4/6] openvswitch: keep vlan tag accelerated on internal device
From: Jiri Benc @ 2016-10-07 16:07 UTC (permalink / raw)
  To: netdev; +Cc: pravin shelar, Eric Garver
In-Reply-To: <cover.1475855896.git.jbenc@redhat.com>

Disallow turning off of vlan acceleration on internal ports. We need the
vlan tag to be in skb->vlan_tci; otherwise, we would pull it back in
ovs_flow_key_extract, defeating the purpose of setting the vlan acceleration
off in the first place.

Signed-off-by: Jiri Benc <jbenc@redhat.com>
---
v3: new in v3
---
 net/openvswitch/vport-internal_dev.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/net/openvswitch/vport-internal_dev.c b/net/openvswitch/vport-internal_dev.c
index e7da29021b38..0531d48eb960 100644
--- a/net/openvswitch/vport-internal_dev.c
+++ b/net/openvswitch/vport-internal_dev.c
@@ -143,6 +143,12 @@ static void internal_set_rx_headroom(struct net_device *dev, int new_hr)
 	dev->needed_headroom = new_hr < 0 ? 0 : new_hr;
 }
 
+static netdev_features_t internal_fix_features(struct net_device *dev,
+					       netdev_features_t features)
+{
+	return features | NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX;
+}
+
 static const struct net_device_ops internal_dev_netdev_ops = {
 	.ndo_open = internal_dev_open,
 	.ndo_stop = internal_dev_stop,
@@ -151,6 +157,7 @@ static const struct net_device_ops internal_dev_netdev_ops = {
 	.ndo_change_mtu = internal_dev_change_mtu,
 	.ndo_get_stats64 = internal_get_stats,
 	.ndo_set_rx_headroom = internal_set_rx_headroom,
+	.ndo_fix_features = internal_fix_features,
 };
 
 static struct rtnl_link_ops internal_dev_link_ops __read_mostly = {
-- 
1.8.3.1

^ permalink raw reply related

* [PATCH net-next v3 3/6] openvswitch: add NETIF_F_HW_VLAN_STAG_TX to internal dev
From: Jiri Benc @ 2016-10-07 16:07 UTC (permalink / raw)
  To: netdev; +Cc: pravin shelar, Eric Garver
In-Reply-To: <cover.1475855896.git.jbenc@redhat.com>

The internal device does support 802.1AD offloading.

Signed-off-by: Jiri Benc <jbenc@redhat.com>
---
v3: new in v3
---
 net/openvswitch/vport-internal_dev.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/openvswitch/vport-internal_dev.c b/net/openvswitch/vport-internal_dev.c
index 95c36147a6e1..e7da29021b38 100644
--- a/net/openvswitch/vport-internal_dev.c
+++ b/net/openvswitch/vport-internal_dev.c
@@ -176,7 +176,7 @@ static void do_setup(struct net_device *netdev)
 
 	netdev->vlan_features = netdev->features;
 	netdev->hw_enc_features = netdev->features;
-	netdev->features |= NETIF_F_HW_VLAN_CTAG_TX;
+	netdev->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX;
 	netdev->hw_features = netdev->features & ~NETIF_F_LLTX;
 
 	eth_hw_addr_random(netdev);
-- 
1.8.3.1

^ permalink raw reply related

* [PATCH net-next v3 2/6] openvswitch: normalize vlan rx path
From: Jiri Benc @ 2016-10-07 16:07 UTC (permalink / raw)
  To: netdev; +Cc: pravin shelar, Eric Garver
In-Reply-To: <cover.1475855896.git.jbenc@redhat.com>

Similarly to how the core networking stack behaves, let the first vlan tag
be always stored in skb->vlan_tci. This is already ensured in
__netif_receive_skb_core for packets that were received from the kernel and
honored by skb_vlan_push and skb_vlan_pop. There is a couple of paths where
a packet with vlan header inside the packet data can be received:

(1) Packets received from the user space.
(2) Packets received via internal device, either injected through AF_PACKET
    or with vlan acceleration turned off.

In addition, there will be a third path when support for ETH_P_TEB packets
is added.

To catch all of these cases, untag the vlan frame in ovs_flow_key_extract
and ovs_flow_key_extract_userspace.

Signed-off-by: Jiri Benc <jbenc@redhat.com>
---
v3: moved the untagging to ovs_flow_key_extract* to catch all cases
---
 net/openvswitch/flow.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c
index f358608dd33d..14159ac19850 100644
--- a/net/openvswitch/flow.c
+++ b/net/openvswitch/flow.c
@@ -716,6 +716,20 @@ static int key_extract(struct sk_buff *skb, struct sw_flow_key *key)
 	return 0;
 }
 
+static struct sk_buff *vlan_untag(struct sk_buff *skb)
+{
+	if (eth_type_vlan(skb->protocol)) {
+		__skb_pull(skb, ETH_HLEN);
+		skb_reset_network_header(skb);
+		skb_reset_mac_len(skb);
+		skb = skb_vlan_untag(skb);
+		if (unlikely(!skb))
+			return NULL;
+		skb_push(skb, ETH_HLEN);
+	}
+	return skb;
+}
+
 int ovs_flow_key_update(struct sk_buff *skb, struct sw_flow_key *key)
 {
 	return key_extract(skb, key);
@@ -727,6 +741,10 @@ struct sk_buff *ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
 {
 	int err;
 
+	skb = vlan_untag(skb);
+	if (!skb)
+		return ERR_PTR(-ENOMEM);
+
 	/* Extract metadata from packet. */
 	if (tun_info) {
 		key->tun_proto = ip_tunnel_info_af(tun_info);
@@ -772,6 +790,10 @@ struct sk_buff *ovs_flow_key_extract_userspace(struct net *net,
 {
 	int err;
 
+	skb = vlan_untag(skb);
+	if (!skb)
+		return ERR_PTR(-ENOMEM);
+
 	/* Extract metadata from netlink attributes. */
 	err = ovs_nla_get_flow_metadata(net, attr, key, log);
 	if (err)
-- 
1.8.3.1

^ permalink raw reply related

* [PATCH net-next v3 0/6] openvswitch: make vlan handling consistent
From: Jiri Benc @ 2016-10-07 16:07 UTC (permalink / raw)
  To: netdev; +Cc: pravin shelar, Eric Garver

Always keep the first vlan tag "accelerated", i.e. in skb->vlan_tci.

Unfortunately, with all the changes since v2, this patchset no longer has
the nice deletions > insertions diffstat. I still think it's worth it, as it
makes things more consistent overall.

Patch 3 is valid on its own. Patch 6 is needed in one form or other; with
the changes in this set, it's a simple deletion. Otherwise we'd need more
elaborate checks.

Jiri Benc (6):
  openvswitch: make skb modifiable in ovs_flow_key_extract*
  openvswitch: normalize vlan rx path
  openvswitch: add NETIF_F_HW_VLAN_STAG_TX to internal dev
  openvswitch: keep vlan tag accelerated on internal device
  openvswitch: remove unreachable code in vlan parsing
  openvswitch: fix vlan subtraction from packet length

 net/openvswitch/datapath.c           |  9 ++--
 net/openvswitch/flow.c               | 83 ++++++++++++++++++++++++------------
 net/openvswitch/flow.h               | 14 +++---
 net/openvswitch/vport-internal_dev.c |  9 +++-
 net/openvswitch/vport.c              | 18 +++-----
 5 files changed, 83 insertions(+), 50 deletions(-)

-- 
1.8.3.1

^ permalink raw reply

* [PATCH net-next v3 1/6] openvswitch: make skb modifiable in ovs_flow_key_extract*
From: Jiri Benc @ 2016-10-07 16:07 UTC (permalink / raw)
  To: netdev; +Cc: pravin shelar, Eric Garver
In-Reply-To: <cover.1475855896.git.jbenc@redhat.com>

Allow ovs_flow_key_extract and ovs_flow_key_extract_userspace to modify the
skb. This will be used by the following patch to move vlan tag to the
vlan_tci field.

Signed-off-by: Jiri Benc <jbenc@redhat.com>
---
v3: new in v3
---
 net/openvswitch/datapath.c |  9 ++++++---
 net/openvswitch/flow.c     | 33 +++++++++++++++++++++++++--------
 net/openvswitch/flow.h     | 14 ++++++++------
 net/openvswitch/vport.c    |  8 +++-----
 4 files changed, 42 insertions(+), 22 deletions(-)

diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index 4d67ea856067..3469a8df3d5e 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -607,10 +607,13 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
 	if (IS_ERR(flow))
 		goto err_kfree_skb;
 
-	err = ovs_flow_key_extract_userspace(net, a[OVS_PACKET_ATTR_KEY],
-					     packet, &flow->key, log);
-	if (err)
+	packet = ovs_flow_key_extract_userspace(net, a[OVS_PACKET_ATTR_KEY],
+						packet, &flow->key, log);
+	if (IS_ERR(packet)) {
+		err = PTR_ERR(packet);
+		packet = NULL;
 		goto err_flow_free;
+	}
 
 	err = ovs_nla_copy_actions(net, a[OVS_PACKET_ATTR_ACTIONS],
 				   &flow->key, &acts, log);
diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c
index c8c82e109c68..f358608dd33d 100644
--- a/net/openvswitch/flow.c
+++ b/net/openvswitch/flow.c
@@ -721,9 +721,12 @@ int ovs_flow_key_update(struct sk_buff *skb, struct sw_flow_key *key)
 	return key_extract(skb, key);
 }
 
-int ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
-			 struct sk_buff *skb, struct sw_flow_key *key)
+struct sk_buff *ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
+				     struct sk_buff *skb,
+				     struct sw_flow_key *key)
 {
+	int err;
+
 	/* Extract metadata from packet. */
 	if (tun_info) {
 		key->tun_proto = ip_tunnel_info_af(tun_info);
@@ -753,19 +756,33 @@ int ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
 	key->ovs_flow_hash = 0;
 	key->recirc_id = 0;
 
-	return key_extract(skb, key);
+	err = key_extract(skb, key);
+	if (err) {
+		kfree_skb(skb);
+		return ERR_PTR(err);
+	}
+	return skb;
 }
 
-int ovs_flow_key_extract_userspace(struct net *net, const struct nlattr *attr,
-				   struct sk_buff *skb,
-				   struct sw_flow_key *key, bool log)
+struct sk_buff *ovs_flow_key_extract_userspace(struct net *net,
+					       const struct nlattr *attr,
+					       struct sk_buff *skb,
+					       struct sw_flow_key *key,
+					       bool log)
 {
 	int err;
 
 	/* Extract metadata from netlink attributes. */
 	err = ovs_nla_get_flow_metadata(net, attr, key, log);
 	if (err)
-		return err;
+		goto err_free;
 
-	return key_extract(skb, key);
+	err = key_extract(skb, key);
+	if (err)
+		goto err_free;
+	return skb;
+
+err_free:
+	kfree_skb(skb);
+	return ERR_PTR(err);
 }
diff --git a/net/openvswitch/flow.h b/net/openvswitch/flow.h
index ae783f5c6695..7eb251f1471f 100644
--- a/net/openvswitch/flow.h
+++ b/net/openvswitch/flow.h
@@ -224,12 +224,14 @@ void ovs_flow_stats_clear(struct sw_flow *);
 u64 ovs_flow_used_time(unsigned long flow_jiffies);
 
 int ovs_flow_key_update(struct sk_buff *skb, struct sw_flow_key *key);
-int ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
-			 struct sk_buff *skb,
-			 struct sw_flow_key *key);
+struct sk_buff *ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
+				     struct sk_buff *skb,
+				     struct sw_flow_key *key);
 /* Extract key from packet coming from userspace. */
-int ovs_flow_key_extract_userspace(struct net *net, const struct nlattr *attr,
-				   struct sk_buff *skb,
-				   struct sw_flow_key *key, bool log);
+struct sk_buff *ovs_flow_key_extract_userspace(struct net *net,
+					       const struct nlattr *attr,
+					       struct sk_buff *skb,
+					       struct sw_flow_key *key,
+					       bool log);
 
 #endif /* flow.h */
diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c
index 8f198437c724..8aefcb20cc58 100644
--- a/net/openvswitch/vport.c
+++ b/net/openvswitch/vport.c
@@ -455,11 +455,9 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
 	}
 
 	/* Extract flow from 'skb' into 'key'. */
-	error = ovs_flow_key_extract(tun_info, skb, &key);
-	if (unlikely(error)) {
-		kfree_skb(skb);
-		return error;
-	}
+	skb = ovs_flow_key_extract(tun_info, skb, &key);
+	if (IS_ERR(skb))
+		return PTR_ERR(skb);
 	ovs_dp_process_packet(skb, &key);
 	return 0;
 }
-- 
1.8.3.1

^ permalink raw reply related

* [PATCH] wlcore: Allow scans when in AP mode
From: Xander Huff @ 2016-10-07 15:45 UTC (permalink / raw)
  To: kvalo-sgV2jX0FEOL9JmXXK+q4OQ
  Cc: linux-wireless-u79uwXL29TY76Z2rM5mHXA,
	netdev-u79uwXL29TY76Z2rM5mHXA, James Minor,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, xander.huff-acOepvfBmUk

From: James Minor <james.minor-acOepvfBmUk@public.gmane.org>

When in AP mode, scans can be done without changing firmware to
the multi-role firmware. Allow the interface to scan if forced
in the scan request.

Signed-off-by: James Minor <james.minor-acOepvfBmUk@public.gmane.org>
Signed-off-by: Xander Huff <xander.huff-acOepvfBmUk@public.gmane.org>
Reviewed-by: Ben Shelton <ben.shelton-acOepvfBmUk@public.gmane.org>
Reviewed-by: Jaeden Amero <jaeden.amero-acOepvfBmUk@public.gmane.org>
---
 drivers/net/wireless/ti/wlcore/main.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c
index 471521a..01ca370 100644
--- a/drivers/net/wireless/ti/wlcore/main.c
+++ b/drivers/net/wireless/ti/wlcore/main.c
@@ -6120,6 +6120,8 @@ static int wl1271_init_ieee80211(struct wl1271 *wl)
 				WIPHY_FLAG_SUPPORTS_SCHED_SCAN |
 				WIPHY_FLAG_HAS_CHANNEL_SWITCH;
 
+	wl->hw->wiphy->features |= NL80211_FEATURE_AP_SCAN;
+
 	/* make sure all our channels fit in the scanned_ch bitmask */
 	BUILD_BUG_ON(ARRAY_SIZE(wl1271_channels) +
 		     ARRAY_SIZE(wl1271_channels_5ghz) >
-- 
1.9.1

^ permalink raw reply related

* [PATCH 07/10] mtd: Add SAM Flash driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Guenter Roeck <groeck@juniper.net>

Add driver for the flash block in Juniper's SAM FPGA.

This driver is used for updating the Altera's EPCS(64,256)
configuration flash devices via a Juniper defined hardware
interface.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/mtd/devices/Kconfig     |  11 +
 drivers/mtd/devices/Makefile    |   1 +
 drivers/mtd/devices/sam-flash.c | 642 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 654 insertions(+)
 create mode 100644 drivers/mtd/devices/sam-flash.c

diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index d4255fb..f5a9032 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -144,6 +144,17 @@ config MTD_LART
 	  not need any mapping/chip driver for LART. This one does it all
 	  for you, so go disable all of those if you enabled some of them (:
 
+config MTD_SAM_FLASH
+	tristate "Juniper SAM Flash driver"
+	depends on MFD_JUNIPER_SAM || MFD_JUNIPER_CBC
+	default y if MFD_JUNIPER_SAM
+	help
+	  This enables the flash driver for the SAM FPGA which is present
+	  on relevant Juniper platforms.
+
+	  This driver can also be built as a module. When it is so the name of
+	  the module is flash-sam.
+
 config JNX_PMB_NVRAM
 	tristate "Juniper FPC PMB NVRAM Driver"
 	depends on (PTXPMB_COMMON || JNX_PTX_NGPMB)
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index b407c5fc..7556311 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MTD_BCM47XXSFLASH)	+= bcm47xxsflash.o
 obj-$(CONFIG_MTD_ST_SPI_FSM)    += st_spi_fsm.o
 obj-$(CONFIG_MTD_POWERNV_FLASH)	+= powernv_flash.o
 
+obj-$(CONFIG_MTD_SAM_FLASH)	+= sam-flash.o
 obj-$(CONFIG_JNX_PMB_NVRAM)     += jnx_pmb_nvram.o
 
 CFLAGS_docg3.o			+= -I$(src)
diff --git a/drivers/mtd/devices/sam-flash.c b/drivers/mtd/devices/sam-flash.c
new file mode 100644
index 0000000..5f071e6
--- /dev/null
+++ b/drivers/mtd/devices/sam-flash.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2012 Juniper networks
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#define SAM_FLASH_DEBUG_ENABLED
+#undef  T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+#define SAM_FLASH_IF_READ_MAX_SIZE		32	/* 256?! */
+
+#define SAM_FLASH_BASE		0x300
+
+#define ADDR_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x000)
+#define COUNTER_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x004)
+#define CONTROL_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x008)
+#define STATUS_REG(x)		((x)->membase + SAM_FLASH_BASE + 0x00c)
+#define WRITE_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x100)
+#define READ_DATA_REG(x)	((x)->membase + SAM_FLASH_BASE + 0x200)
+
+static int sam_flash_if_read_max_size = SAM_FLASH_IF_READ_MAX_SIZE;
+module_param(sam_flash_if_read_max_size, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_if_read_max_size,
+		 "maximum read size done by SAM flash IF");
+
+#ifdef SAM_FLASH_DEBUG_ENABLED
+
+static int sam_flash_debug;
+module_param(sam_flash_debug, int, S_IRUSR | S_IRGRP | S_IWUSR);
+MODULE_PARM_DESC(sam_flash_debug, "enable debugging information");
+
+#define SAM_FLASH_DEBUG(dev, fmt, args...)			\
+	do {							\
+		if (sam_flash_debug) {				\
+			dev_info(dev, fmt, ## args);		\
+		}						\
+	} while (0)
+#else /* SAM_FLASH_DEBUG_ENABLED */
+#define SAM_FLASH_DEBUG(dev, fmt, args...)      {}
+#endif /* SAM_FLASH_DEBUG_ENABLED */
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS_EXT_STS_ID(sts)		((u8)((sts >> 8) & 0xff))
+#define EPCS_EXT_STS_RDSTS(sts)		((u8)((sts >> 16) & 0xff))
+#define EPCS_EXT_STS_SID(sts)		((u8)((sts >> 24) & 0xff))
+/* EPCS Device "read status" bits */
+#define EPCS_STS_WIP_BIT		0x01
+#define EPCS_STS_WLE_BIT		0x02
+#define EPCS_STS_BP_BITS(status)	((status >> 2) & 0x7)
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ */
+#define EPCS64_S_ID			0x16
+#define EPCS64_NAME			"Altera EPCS64"
+#define EPCS64_SECT_SZ_SHIFT		16
+#define EPCS64_SECTOR_SIZE		(1 << EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_SECTORS			128
+#define EPCS64_ADDR_TO_SECTOR(_addr)	((_addr) >> EPCS64_SECT_SZ_SHIFT)
+#define EPCS64_PAGE_SIZE		256
+#define EPCS64_PAGES			32768
+#define EPCS64_SIZE			(EPCS64_PAGE_SIZE * EPCS64_PAGES)
+#define EPCS64_MIN_SECT(bp_bits)	(EPCS64_SECTORS - (1 << bp_bits))
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ *  timeout for busy:   t5e-pic/t5e_flash.c
+ */
+#define EPCS_TIMEOUT_BUSY		1
+#define EPCS_TIMEOUT_SINGLE_BYTE_READ	3
+#define EPCS_TIMEOUT_READ_ID		3
+#define EPCS_TIMEOUT_READ_STATUS	3
+
+/*
+ *  Ref: pfe/common/toolkis/flash/altera_epcs_as.h
+ *  timeout for waiting completion:   t5e-pic/t5e_flash.c
+ */
+#define EPCS_RD_TIMEO		20
+#define EPCS_WR_TIMEO		25
+#define EPCS_BLK_WR_TIMEO	25
+#define EPCS_SC_ER_TIMEO	(10 * 1000)
+#define EPCS_SC_PRT_TIMEO	35
+#define EPCS_CH_ER_TIMEO	(200 * 1000)
+/* */
+#define EPCS_STS_BSY_BIT	0x01
+#define EPCS_ILLEGAL_WR_BIT	0x02
+#define EPCS_ILLEGAL_RD_BIT	0x04
+#define EPCS_ILLEGAL		(EPCS_ILLEGAL_WR_BIT | EPCS_ILLEGAL_RD_BIT)
+#define EPCS_STATUS_BUSY(s)	((s) & EPCS_STS_BSY_BIT)
+
+/*
+ *  Ref t5e-pic/t5e_flash.c
+ */
+#define EPCS_BUSY_POLLING_START_DELAY		100	/* us */
+#define EPCS_BUSY_POLLING_START_DELAY_CNT	10
+#define EPCS_BUSY_POLLING_DELAY		(EPCS_BUSY_POLLING_START_DELAY_CNT * \
+					 EPCS_BUSY_POLLING_START_DELAY)
+
+/*
+ * FPGA flash control register: t5e-pic/t5e_fpga.h
+ */
+#define SAM_FLASH_IF_CONTROL_READ_SID		0x00000080
+#define SAM_FLASH_IF_CONTROL_CHIP_ERASE		0x00000040
+#define SAM_FLASH_IF_CONTROL_SECTOR_ERASE	0x00000020
+#define SAM_FLASH_IF_CONTROL_SECTOR_PROTECT	0x00000010
+#define SAM_FLASH_IF_CONTROL_READ_STATUS	0x00000008
+#define SAM_FLASH_IF_CONTROL_READ_ID		0x00000004
+#define SAM_FLASH_IF_CONTROL_WRITE		0x00000002
+#define SAM_FLASH_IF_CONTROL_READ		0x00000001
+#define SAM_FLASH_IF_WRITE_REG_SIZE		sizeof(u32)
+#define SAM_FLASH_IF_READ_REG_SIZE		sizeof(u32)
+
+struct sam_flash_info {
+	const char *name;
+	u8 device_id;
+	size_t flash_size;
+	size_t page_size;
+	size_t nr_pages;
+	size_t nr_sectors;
+	size_t erasesize;
+	size_t writesize;
+	size_t writebufsize;
+};
+
+static struct sam_flash_info sam_flash_info_db[] = {
+	{
+		.name = EPCS64_NAME,
+		.device_id = EPCS64_S_ID,
+		.flash_size = EPCS64_SIZE,
+		.page_size = EPCS64_PAGE_SIZE,
+		.nr_pages = EPCS64_PAGES,
+		.nr_sectors = EPCS64_SECTORS,
+		.erasesize = EPCS64_SECTOR_SIZE,
+		.writesize = 4,
+		.writebufsize = 4,
+	},
+};
+
+#define SAM_FLASH_INFO_DB_SIZE	ARRAY_SIZE(sam_flash_info_db)
+
+/**
+ * struct sam_flash - SAM FLASH private data structure.
+ * @membase:		PCI base address of Memory mapped I/O register.
+ * @reg:		Memory mapped PCH GPIO register list.
+ * @dev:		Pointer to device structure.
+ */
+struct sam_flash {
+	void __iomem *membase;
+	struct mutex lock;
+	struct device *dev;
+	struct sam_flash_info *info;
+	struct mtd_info mtd_info;
+};
+
+#define mtd_to_sam_flash(mtd) container_of(mtd, struct sam_flash, mtd_info)
+
+static bool sam_flash_if_busy(struct sam_flash *sam_flash, int retry)
+{
+	u32 status;
+
+	do {
+		status = ioread32(STATUS_REG(sam_flash));
+		if (!EPCS_STATUS_BUSY(status))
+			return false;
+		if (retry <= 1)
+			return true;
+		usleep_range(50, 100);
+	} while (--retry >= 0);
+
+	return true;
+}
+
+static int
+sam_flash_if_busy_wait(struct sam_flash *sam_flash, unsigned int max_wait_msec)
+{
+	unsigned long timeout;
+	u32 status;
+
+	timeout = jiffies + msecs_to_jiffies(max_wait_msec);
+	udelay(50);
+
+	do {
+		status = ioread32(STATUS_REG(sam_flash));
+		if (!EPCS_STATUS_BUSY(status))
+			return 0;
+
+		if (status & EPCS_ILLEGAL)
+			return -EACCES;
+
+		usleep_range(50, 100);
+	} while (time_before(jiffies, timeout));
+
+	return -ETIMEDOUT;
+}
+
+static int
+sam_flash_mem_read(struct sam_flash *sam_flash, u32 offset,
+		   u8 *data, size_t len)
+{
+	struct sam_flash_info *info = sam_flash->info;
+	void __iomem *io_addr;
+	u32 io_data;
+	int i, cnt;
+
+	if (offset >= info->flash_size || offset + len > info->flash_size)
+		return -EINVAL;
+
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY))
+		return -ETIMEDOUT;
+
+	iowrite32(len - 1, COUNTER_REG(sam_flash));
+	iowrite32(offset, ADDR_REG(sam_flash));
+
+	/* trigger the read */
+	iowrite32(SAM_FLASH_IF_CONTROL_READ, CONTROL_REG(sam_flash));
+	ioread32(CONTROL_REG(sam_flash));
+
+	/*
+	 * Before we start polling the busy bit, wait for some time,
+	 * so that, the busy bit will go high
+	 */
+#ifdef T5E_MAX_FLASH_READ_WAIT_TIME_FIXED
+	udelay(50);
+#else /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+	udelay(50 * ((len >> 2) + 1));	/* 50 usec every 4 bytes */
+#endif /* T5E_MAX_FLASH_READ_WAIT_TIME_FIXED */
+
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_SINGLE_BYTE_READ))
+		return -ETIMEDOUT;
+
+	io_data = ioread32(COUNTER_REG(sam_flash));
+	if (io_data != len - 1)
+		return -EIO;
+
+	SAM_FLASH_DEBUG(sam_flash->dev,
+			"%s BYTE_CNT: len: %u, io_data: %u.\n",
+			__func__, (unsigned int)len, io_data);
+
+	io_addr = READ_DATA_REG(sam_flash);
+	for (cnt = 0; cnt < len; io_addr += sizeof(u32)) {
+		io_data = ioread32(io_addr);
+		for (i = 0; i < sizeof(u32) && cnt < len; i++, cnt++)
+			*(data++) = (io_data >> (i << 3)) & 0xff;
+	}
+
+	return 0;
+}
+
+static int sam_flash_read_sid(struct sam_flash *sam_flash)
+{
+	u32 io_data;
+
+	iowrite32(SAM_FLASH_IF_CONTROL_READ_SID, CONTROL_REG(sam_flash));
+	ioread32(CONTROL_REG(sam_flash));
+
+	/*
+	 * Before we start polling the busy bit, wait for some time
+	 * to ensure that busy bit is high.
+	 */
+	udelay(EPCS_BUSY_POLLING_DELAY);
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+		return -ETIMEDOUT;
+
+	io_data = ioread32(STATUS_REG(sam_flash));
+
+	return EPCS_EXT_STS_SID(io_data);
+}
+
+static struct sam_flash_info *sam_flash_get_info(struct sam_flash *sam_flash)
+{
+	struct sam_flash_info *info;
+	u8 sid;
+	int idx;
+
+	sid = sam_flash_read_sid(sam_flash);
+	if (sid < 0)
+		return ERR_PTR(sid);
+
+	info = ERR_PTR(-EINVAL);
+	for (idx = 0; idx < SAM_FLASH_INFO_DB_SIZE; idx++) {
+		if (sam_flash_info_db[idx].device_id == sid) {
+			info = &sam_flash_info_db[idx];
+			break;
+		}
+	}
+	return info;
+}
+
+static inline int sam_flash_get_page_num(struct sam_flash *sam_flash,
+					 u32 offset)
+{
+	return offset / sam_flash->info->page_size;
+}
+
+static int sam_flash_mem_write(struct sam_flash *sam_flash, u32 offset,
+			       const u8 *data, size_t len)
+{
+	struct sam_flash_info *info = sam_flash->info;
+	int status, bytes_in_reg, cnt, cnt2;
+	int start_page, end_page;
+	void __iomem *io_addr;
+	u32 io_data;
+	const u8 *buf;
+
+	start_page = sam_flash_get_page_num(sam_flash, offset);
+	end_page = sam_flash_get_page_num(sam_flash, offset + len - 1);
+
+	/*
+	 *  Based on Altera EPCS Device Datasheet,
+	 *  Writing with multiple byte must be in the __SAME__ page.
+	 *  Not sure if SAM FPGA takes that so ...
+	 */
+	if (len > info->page_size ||
+	    start_page != end_page ||
+	    start_page >= info->nr_pages) {
+		dev_err(sam_flash->dev, "Bad write length / offset\n");
+		return -EINVAL;
+	}
+
+	/* check if FPGA is ready to accept new command */
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_BUSY)) {
+		dev_err(sam_flash->dev, "chip is busy\n");
+		return -ETIMEDOUT;
+	}
+
+	iowrite32(len - 1, COUNTER_REG(sam_flash));
+
+	/* copy the data to WRITE_DATA register */
+	io_addr = WRITE_DATA_REG(sam_flash);
+	for (buf = data, cnt = 0; cnt < len;) {
+		bytes_in_reg = len - cnt;
+		if (bytes_in_reg > SAM_FLASH_IF_WRITE_REG_SIZE)
+			bytes_in_reg = SAM_FLASH_IF_WRITE_REG_SIZE;
+		io_data = 0;
+		for (cnt2 = 0; cnt2 < bytes_in_reg; cnt2++, buf++)
+			io_data |= *buf << (cnt2 << 3);
+
+		iowrite32(io_data, io_addr);
+		cnt += bytes_in_reg;
+		io_addr += bytes_in_reg;
+	}
+
+	iowrite32(offset, ADDR_REG(sam_flash));
+	/* trigger the write */
+	iowrite32(SAM_FLASH_IF_CONTROL_WRITE, CONTROL_REG(sam_flash));
+	ioread32(CONTROL_REG(sam_flash));
+
+	status = sam_flash_if_busy_wait(sam_flash, EPCS_WR_TIMEO);
+	return status;
+}
+
+static int sam_flash_mem_is_protected(struct sam_flash *sam_flash,
+				      u32 offset, size_t len)
+{
+	struct sam_flash_info *info = sam_flash->info;
+	u32 flash_status;
+	u32 sector;
+	u32 io_data;
+	int status = 0;
+
+	iowrite32(SAM_FLASH_IF_CONTROL_READ_STATUS, CONTROL_REG(sam_flash));
+	if (sam_flash_if_busy(sam_flash, EPCS_TIMEOUT_READ_ID))
+		return -ETIMEDOUT;
+
+	io_data = ioread32(STATUS_REG(sam_flash));
+	flash_status = EPCS_EXT_STS_RDSTS(io_data);
+	sector = EPCS64_ADDR_TO_SECTOR(offset);
+	if (EPCS_STS_BP_BITS(flash_status) &&
+	    sector < info->erasesize &&
+	    sector >= EPCS64_MIN_SECT(EPCS_STS_BP_BITS(flash_status))) {
+		status = -EACCES;
+		SAM_FLASH_DEBUG(sam_flash->dev,
+				"%s offset: 0x%x, len: %u: PROTECTED(0x%x): %d.\n",
+				__func__, offset, (unsigned int)len,
+				flash_status, status);
+	}
+
+	return status;
+}
+
+static int sam_flash_erase_sector(struct sam_flash *sam_flash, u32 offset)
+{
+	iowrite32(offset, ADDR_REG(sam_flash));
+	iowrite32(SAM_FLASH_IF_CONTROL_SECTOR_ERASE, CONTROL_REG(sam_flash));
+
+	return sam_flash_if_busy_wait(sam_flash, EPCS_SC_ER_TIMEO);
+}
+
+static int sam_flash_erase(struct mtd_info *mtd_info,
+			   struct erase_info *erase_info)
+{
+	struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+	u32 len, start, end, offset;
+	int status = 0;
+
+	len = (u32) erase_info->len;
+	start = (u32) erase_info->addr;
+	end = start + len - 1;
+
+	offset = start;
+	mutex_lock(&sam_flash->lock);
+	erase_info->state = MTD_ERASE_DONE;
+	while (offset < end) {
+		status = sam_flash_erase_sector(sam_flash, offset);
+		if (status) {
+			erase_info->state = MTD_ERASE_FAILED;
+			break;
+		}
+		offset += mtd_info->erasesize;
+	}
+	mutex_unlock(&sam_flash->lock);
+
+	mtd_erase_callback(erase_info);
+
+	return status;
+}
+
+static int sam_flash_read(struct mtd_info *mtd_info, loff_t from, size_t len,
+			  size_t *retlen, unsigned char *buf)
+{
+	struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+	int cnt, max_cnt;
+	int status = 0;
+
+	*retlen = 0;
+
+	max_cnt = len / sam_flash_if_read_max_size;
+	if (len % sam_flash_if_read_max_size != 0)
+		max_cnt++;
+	mutex_lock(&sam_flash->lock);
+	for (cnt = 0; cnt < max_cnt; cnt++) {
+		u32 from2;
+		size_t len2;
+		u8 *buf2;
+
+		from2 = from + *retlen;
+		buf2 = buf + *retlen;
+		len2 = len - *retlen;
+		if (len2 > sam_flash_if_read_max_size)
+			len2 = sam_flash_if_read_max_size;
+
+		status = sam_flash_mem_read(sam_flash, from2, buf2, len2);
+		if (status != 0) {
+			dev_err(sam_flash->dev,
+				"RD: cnt: %04d(%04d): from: %u(%u), len: %u(%u) failed: %d.\n",
+				cnt, max_cnt, from2, (u32) from,
+				(unsigned int)len2, (unsigned int)len, status);
+			break;
+		}
+		*retlen += len2;
+	}
+	mutex_unlock(&sam_flash->lock);
+
+	return status;
+}
+
+static int sam_flash_write(struct mtd_info *mtd_info, loff_t to,
+			   size_t len, size_t *retlen, const unsigned char *buf)
+{
+	struct sam_flash *sam_flash = mtd_to_sam_flash(mtd_info);
+	int status, done, to_be_done;
+
+	mutex_lock(&sam_flash->lock);
+	status = sam_flash_mem_is_protected(sam_flash, to, len);
+	if (status)
+		goto abort;
+
+	for (done = 0; done < len; done += to_be_done) {
+		to_be_done = to & (sam_flash->info->page_size - 1);
+		if (to_be_done == 0) {
+			/* 'to' is page aligned */
+			to_be_done = len - done;
+			if (to_be_done > sam_flash->info->page_size)
+				to_be_done = sam_flash->info->page_size;
+		} else {
+			to_be_done = sam_flash->info->page_size - to_be_done;
+		}
+
+		SAM_FLASH_DEBUG(sam_flash->dev,
+				"%s to: 0x%x, buf: 0x%p, to_be_done: %d, done: %d, len: %d.\n",
+				__func__,
+				(u32) to, buf, to_be_done,
+				done, (unsigned int)len);
+		status = sam_flash_mem_write(sam_flash, to, buf, to_be_done);
+		if (status) {
+			dev_err(sam_flash->dev,
+				"WR: failed to 0x%x, buf: 0x%p, done: %d(%d), to_be_done: %d: %d.\n",
+				(u32)to, buf, done, (unsigned int)len,
+				to_be_done, status);
+			break;
+		}
+
+		to += to_be_done;
+		buf += to_be_done;
+	}
+
+	if (!status)
+		*retlen = len;
+
+abort:
+	mutex_unlock(&sam_flash->lock);
+	return status;
+}
+
+static int sam_flash_mtd_attach(struct platform_device *pdev,
+				struct sam_flash *sam_flash)
+{
+	struct mtd_part_parser_data ppdata = {};
+	struct sam_flash_info *info;
+	struct device *dev = sam_flash->dev;
+	struct mtd_info *mtd_info;
+	int ret;
+
+	info = sam_flash_get_info(sam_flash);
+	if (IS_ERR(info))
+		return PTR_ERR(info);
+
+	sam_flash->info = info;
+
+	mtd_info = &sam_flash->mtd_info;
+	mtd_info->name = dev_name(dev);
+	mtd_info->type = MTD_NORFLASH;
+	mtd_info->flags = MTD_CAP_NORFLASH;
+	mtd_info->erasesize = info->erasesize;
+	mtd_info->writesize = info->writesize;
+	mtd_info->writebufsize = info->writebufsize;
+	mtd_info->size = info->flash_size;
+	mtd_info->_erase = sam_flash_erase;
+	mtd_info->_read = sam_flash_read;
+	mtd_info->_write = sam_flash_write;
+
+	ret = mtd_device_parse_register(mtd_info, NULL, &ppdata, NULL, 0);
+	if (ret) {
+		dev_err(dev, "mtd_device_parse_register returned %d\n", ret);
+		return ret;
+	}
+
+	dev_info(dev,
+		 "ATTACH: name: \"%s\" type: %d, flags: 0x%x.\n",
+		 mtd_info->name, mtd_info->type, mtd_info->flags);
+	dev_info(dev,
+		 "ATTACH: erasesize: %d, writesize: %d, writebufsize: %d\n",
+		 mtd_info->erasesize, mtd_info->writesize,
+		 mtd_info->writebufsize);
+	dev_info(dev,
+		 "ATTACH: size: %llu.%u(%llu KB).\n",
+		 mtd_info->size,
+		 (unsigned int)info->flash_size,
+		 (long long)mtd_info->size >> 10);
+
+	return 0;
+}
+
+static int sam_flash_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sam_flash *sam_flash;
+	struct resource *res;
+
+	sam_flash = devm_kzalloc(dev, sizeof(*sam_flash), GFP_KERNEL);
+	if (sam_flash == NULL)
+		return -ENOMEM;
+
+	sam_flash->dev = dev;
+	platform_set_drvdata(pdev, sam_flash);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENOMEM;
+
+	sam_flash->membase = devm_ioremap(dev, res->start, resource_size(res));
+	if (!sam_flash->membase)
+		return -ENOMEM;
+
+	mutex_init(&sam_flash->lock);
+
+	return sam_flash_mtd_attach(pdev, sam_flash);
+}
+
+static int sam_flash_remove(struct platform_device *pdev)
+{
+	struct sam_flash *sam_flash = platform_get_drvdata(pdev);
+
+	mtd_device_unregister(&sam_flash->mtd_info);
+
+	return 0;
+}
+
+static const struct of_device_id sam_flash_ids[] = {
+	{ .compatible = "jnx,flash-sam", },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, sam_flash_ids);
+
+static struct platform_driver sam_flash_driver = {
+	.driver = {
+		   .name = "flash-sam",
+		   .owner = THIS_MODULE,
+		   .of_match_table = sam_flash_ids,
+		   },
+	.probe = sam_flash_probe,
+	.remove = sam_flash_remove,
+};
+
+static int __init sam_flash_init(void)
+{
+	return platform_driver_register(&sam_flash_driver);
+}
+
+static void __exit sam_flash_exit(void)
+{
+	platform_driver_unregister(&sam_flash_driver);
+}
+
+module_init(sam_flash_init);
+module_exit(sam_flash_exit);
+
+MODULE_DESCRIPTION("SAM-FPGA FLASH Driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

^ permalink raw reply related

* Re: [PATCH] net: macb: NULL out phydev after removing mdio bus
From: Moritz Fischer @ 2016-10-07 15:20 UTC (permalink / raw)
  To: Xander Huff
  Cc: Nicolas Ferre, netdev, Nathan Sullivan, Linux Kernel Mailing List,
	Brad Mouring
In-Reply-To: <1475853202-19903-1-git-send-email-xander.huff@ni.com>

On Fri, Oct 7, 2016 at 8:13 AM, Xander Huff <xander.huff@ni.com> wrote:
> From: Nathan Sullivan <nathan.sullivan@ni.com>
>
> To ensure the dev->phydev pointer is not used after becoming invalid in
> mdiobus_unregister, set it to NULL. This happens when removing the macb
> driver without first taking its interface down, since unregister_netdev
> will end up calling macb_close.
>
> Signed-off-by: Xander Huff <xander.huff@ni.com>
> Signed-off-by: Nathan Sullivan <nathan.sullivan@ni.com>
> Signed-off-by: Brad Mouring <brad.mouring@ni.com>
Reviewed-by: Moritz Fischer <moritz.fischer@ettus.com>

^ permalink raw reply

* [PATCH 4/4] gpio: ptxpmb-ext-cpld: Document bindings of PTXPMB extended CPLD
From: Pantelis Antoniou @ 2016-10-07 15:19 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Georgi Vlaev, Guenter Roeck,
	JawaharBalaji Thirumalaisamy, Pantelis Antoniou, devicetree,
	linux-kernel, linux-gpio, linux-i2c, linux-mtd, linux-watchdog,
	netdev
In-Reply-To: <1475853574-22339-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add device tree bindings document for the GPIO driver of
Juniper's PTXPMB extended CPLD.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../bindings/gpio/jnx,gpio-ptxpmb-ext-cpld.txt     | 36 ++++++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-ext-cpld.txt

diff --git a/Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-ext-cpld.txt b/Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-ext-cpld.txt
new file mode 100644
index 0000000..87f01b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-ext-cpld.txt
@@ -0,0 +1,36 @@
+Juniper PTXPMB extended CPLD GPIO block
+
+Required properties:
+
+- compatible:
+    Must be "jnx,gpio-ptxpmb-ext-cpld"
+
+- #gpio-cells:
+    Should be <2>.  The first cell is the pin number (within the controller's
+    pin space), and the second is used for the following flags:
+	bit[0]: direction (0 = out, 1 = in)
+	bit[1]: init high
+	bit[2]: active low
+
+- gpio-controller:
+    Specifies that the node is a GPIO controller.
+
+- interrupt-controller:
+    Specifies that the node is an interrupt controller.
+
+Optional properties:
+
+- reg:
+    Address and length of the register set for the device. Usually supplied
+    by the parent MFD device.
+
+
+Example:
+
+gpio_ext_cpld: cpld-ext-gpio {
+	compatible = "jnx,gpio-ptxpmb-ext-cpld";
+	#gpio-cells = <2>;
+	#interrupt-cells = <2>;
+	gpio-controller;
+	interrupt-controller;
+};
-- 
1.9.1


^ permalink raw reply related

* [PATCH 3/4] gpio: ptxpmb-ext-cpld: Add driver for Juniper's PTXPMB extended CPLD
From: Pantelis Antoniou @ 2016-10-07 15:19 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Georgi Vlaev, Guenter Roeck,
	JawaharBalaji Thirumalaisamy, Pantelis Antoniou,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-watchdog-u79uwXL29TY76Z2rM5mHXA,
	netdev-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1475853574-22339-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

This IP block is present in the PTXPMB extended CPLD present on
Junipers PTX series of routers and provides SIB connector status pins
as GPIO pins for use with other drivers.

Signed-off-by: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: JawaharBalaji Thirumalaisamy <jawaharb-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 drivers/gpio/Kconfig                |  11 +
 drivers/gpio/Makefile               |   1 +
 drivers/gpio/gpio-ptxpmb-ext-cpld.c | 430 ++++++++++++++++++++++++++++++++++++
 3 files changed, 442 insertions(+)
 create mode 100644 drivers/gpio/gpio-ptxpmb-ext-cpld.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index c25dbe9..281029b 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -371,6 +371,17 @@ config GPIO_PTXPMB_CPLD
 	  This driver can also be built as a module.  If so, the module
 	  will be called gpio-ptxpmb-cpld.
 
+config GPIO_PTXPMB_EXT_CPLD
+	tristate "PTXPMB Extended CPLD GPIO"
+	depends on MFD_JUNIPER_EXT_CPLD
+	default y if MFD_JUNIPER_EXT_CPLD
+	help
+	  This driver exports various bits on the Juniper Control Board
+	  Extended CPLD as GPIO pins to userspace.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called gpio-ptxpmb-ext-cpld.
+
 config GPIO_PXA
 	bool "PXA GPIO support"
 	depends on ARCH_PXA || ARCH_MMP
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 6691d8c..ec890c7 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -91,6 +91,7 @@ obj-$(CONFIG_GPIO_PCH)		+= gpio-pch.o
 obj-$(CONFIG_GPIO_PISOSR)	+= gpio-pisosr.o
 obj-$(CONFIG_GPIO_PL061)	+= gpio-pl061.o
 obj-$(CONFIG_GPIO_PTXPMB_CPLD)	+= gpio-ptxpmb-cpld.o
+obj-$(CONFIG_GPIO_PTXPMB_EXT_CPLD) += gpio-ptxpmb-ext-cpld.o
 obj-$(CONFIG_GPIO_PXA)		+= gpio-pxa.o
 obj-$(CONFIG_GPIO_RC5T583)	+= gpio-rc5t583.o
 obj-$(CONFIG_GPIO_RDC321X)	+= gpio-rdc321x.o
diff --git a/drivers/gpio/gpio-ptxpmb-ext-cpld.c b/drivers/gpio/gpio-ptxpmb-ext-cpld.c
new file mode 100644
index 0000000..0152f0b
--- /dev/null
+++ b/drivers/gpio/gpio-ptxpmb-ext-cpld.c
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2012 Juniper networks
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+
+#include <linux/mfd/ptxpmb_ext_cpld.h>
+
+#define EXT_CPLD_NGPIO	32	/*  0..15: SIB presence bits	*/
+				/* 16..31: SIB interrupt status	*/
+
+/**
+ * struct ext_cpld_gpio - GPIO private data structure.
+ * @base: PCI base address of Memory mapped I/O register.
+ * @dev: Pointer to device structure.
+ * @gpio: Data for GPIO infrastructure.
+ */
+struct ext_cpld_gpio {
+	void __iomem *base;
+	struct device *dev;
+	struct gpio_chip gpio;
+	struct mutex irq_lock;
+	struct mutex work_lock;
+	struct irq_domain *domain;
+	int irq;
+	u8 irq_type[EXT_CPLD_NGPIO];
+	u16 sib_presence_cache;
+	u16 sib_presence_irq_enabled;
+	u16 sib_irq_status_cache;
+	u16 sib_irq_enabled;
+	struct delayed_work work;
+};
+
+static int ext_cpld_gpio_get(struct gpio_chip *gpio, unsigned int nr)
+{
+	struct ext_cpld_gpio *chip = container_of(gpio,
+						  struct ext_cpld_gpio, gpio);
+	struct pmb_boot_cpld_ext *cpld = chip->base;
+	u16 *addr = nr < 16 ? &cpld->sib_presence : &cpld->sib_irq_status;
+	u16 val;
+
+	val = ioread16(addr);
+	if (nr < 16)
+		chip->sib_presence_cache = val;
+	else
+		chip->sib_irq_status_cache = val;
+
+	return !!(val & (1 << (nr & 15)));
+}
+
+static int ext_cpld_gpio_direction_input(struct gpio_chip *gpio,
+					 unsigned int nr)
+{
+	/* all pins are input pins */
+	return 0;
+}
+
+static int ext_cpld_gpio_to_irq(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct ext_cpld_gpio *chip = container_of(gpio,
+						  struct ext_cpld_gpio, gpio);
+
+	return irq_create_mapping(chip->domain, offset);
+}
+
+static void ext_cpld_irq_mask(struct irq_data *data)
+{
+	struct ext_cpld_gpio *chip = irq_data_get_irq_chip_data(data);
+	struct pmb_boot_cpld_ext *cpld = chip->base;
+	u16 *addr = data->hwirq < 16 ?
+		&cpld->sib_presence_irq_en : &cpld->sib_irq_en;
+	u16 mask = 1 << (data->hwirq & 0x0f);
+
+	if (chip->irq)
+		iowrite16(ioread16(addr) & ~mask, addr);
+
+	if (data->hwirq < 16)
+		chip->sib_presence_irq_enabled &= ~mask;
+	else
+		chip->sib_irq_enabled &= ~mask;
+}
+
+static void ext_cpld_irq_unmask(struct irq_data *data)
+{
+	struct ext_cpld_gpio *chip = irq_data_get_irq_chip_data(data);
+	struct pmb_boot_cpld_ext *cpld = chip->base;
+	u16 *addr = data->hwirq < 16 ?
+		&cpld->sib_presence_irq_en : &cpld->sib_irq_en;
+	u16 mask = 1 << (data->hwirq & 0x0f);
+
+	if (chip->irq)
+		iowrite16(ioread16(addr) | mask, addr);
+
+	if (data->hwirq < 16)
+		chip->sib_presence_irq_enabled |= mask;
+	else
+		chip->sib_irq_enabled |= mask;
+}
+
+static int ext_cpld_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct ext_cpld_gpio *chip = irq_data_get_irq_chip_data(data);
+
+	chip->irq_type[data->hwirq] = type & 0x0f;
+
+	return 0;
+}
+
+static void ext_cpld_irq_bus_lock(struct irq_data *data)
+{
+	struct ext_cpld_gpio *chip = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&chip->irq_lock);
+}
+
+static void ext_cpld_irq_bus_unlock(struct irq_data *data)
+{
+	struct ext_cpld_gpio *chip = irq_data_get_irq_chip_data(data);
+
+	/* Synchronize interrupts to chip */
+
+	mutex_unlock(&chip->irq_lock);
+}
+
+static struct irq_chip ext_cpld_irq_chip = {
+	.name = "gpio-ext-cpld",
+	.irq_mask = ext_cpld_irq_mask,
+	.irq_unmask = ext_cpld_irq_unmask,
+	.irq_set_type = ext_cpld_irq_set_type,
+	.irq_bus_lock = ext_cpld_irq_bus_lock,
+	.irq_bus_sync_unlock = ext_cpld_irq_bus_unlock,
+};
+
+static int ext_cpld_gpio_irq_map(struct irq_domain *domain, unsigned int irq,
+				 irq_hw_number_t hwirq)
+{
+	pr_info("ext_cpld_gpio_irq_map irq %d hwirq %d\n", irq, (int)hwirq);
+
+	irq_set_chip_data(irq, domain->host_data);
+	irq_set_chip(irq, &ext_cpld_irq_chip);
+	irq_set_nested_thread(irq, true);
+
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops ext_cpld_gpio_irq_domain_ops = {
+	.map = ext_cpld_gpio_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static void __ext_cpld_gpio_irq_work(struct ext_cpld_gpio *chip,
+				     u16 *datap, u16 *cachep,
+				     unsigned long enabled, int base)
+{
+	u16 data, cache;
+	unsigned int pos;
+
+	cache = *cachep;
+	data = ioread16(datap);
+
+	for_each_set_bit(pos, &enabled, 16) {
+		u16 mask = 1 << pos;
+		u16 bit;
+		int type;
+
+		bit = data & mask;
+		if (bit == (cache & mask))
+			continue;
+
+		type = chip->irq_type[base + pos];
+		/*
+		 * check irq->type for match. Only handle edge triggered
+		 * interrupts; anything else doesn't make sense here.
+		 * TBD: While this is correct for insertion status interrupts,
+		 * we may need to support level triggered interrupts to handle
+		 * the irq status register.
+		 */
+		if (((type & IRQ_TYPE_EDGE_RISING) && bit) ||
+		    ((type & IRQ_TYPE_EDGE_FALLING) && !bit)) {
+			int virq = irq_find_mapping(chip->domain, base + pos);
+
+			handle_nested_irq(virq);
+		}
+	}
+	*cachep = data;
+}
+
+static void ext_cpld_gpio_irq_work(struct ext_cpld_gpio *chip)
+{
+	struct pmb_boot_cpld_ext *cpld = chip->base;
+
+	mutex_lock(&chip->work_lock);
+
+	__ext_cpld_gpio_irq_work(chip, &cpld->sib_presence,
+				 &chip->sib_presence_cache,
+				 chip->sib_presence_irq_enabled,
+				 0);
+
+	__ext_cpld_gpio_irq_work(chip, &cpld->sib_irq_status,
+				 &chip->sib_irq_status_cache,
+				 chip->sib_irq_enabled,
+				 16);
+
+	mutex_unlock(&chip->work_lock);
+}
+
+static irqreturn_t ext_cpld_gpio_irq_handler(int irq, void *data)
+{
+	struct ext_cpld_gpio *chip = data;
+	struct pmb_boot_cpld_ext *cpld = chip->base;
+
+	pr_info("ext_cpld got interrupt %d 0x%x:0x%x\n", irq,
+		ioread16(&cpld->sib_presence),
+		ioread16(&cpld->sib_irq_status));
+
+	ext_cpld_gpio_irq_work(chip);
+
+	return IRQ_HANDLED;
+}
+
+static void ext_cpld_gpio_worker(struct work_struct *work)
+{
+	struct ext_cpld_gpio *chip = container_of(work, struct ext_cpld_gpio,
+						  work.work);
+
+	ext_cpld_gpio_irq_work(chip);
+	schedule_delayed_work(&chip->work, 1);
+}
+
+static int ext_cpld_gpio_irq_setup(struct device *dev,
+				   struct ext_cpld_gpio *chip)
+{
+	int ret;
+
+	chip->domain = irq_domain_add_linear(dev->of_node, EXT_CPLD_NGPIO,
+					     &ext_cpld_gpio_irq_domain_ops,
+					     chip);
+	if (!chip->domain)
+		return -ENOMEM;
+
+	INIT_DELAYED_WORK(&chip->work, ext_cpld_gpio_worker);
+
+	if (chip->irq) {
+		dev_info(dev, "Setting up interrupt %d\n", chip->irq);
+		ret = devm_request_threaded_irq(dev, chip->irq, NULL,
+						ext_cpld_gpio_irq_handler,
+						IRQF_ONESHOT,
+						dev_name(dev), chip);
+		if (ret)
+			goto out_remove_domain;
+	} else {
+		schedule_delayed_work(&chip->work, 1);
+	}
+
+	chip->gpio.to_irq = ext_cpld_gpio_to_irq;
+
+	return 0;
+
+out_remove_domain:
+	irq_domain_remove(chip->domain);
+	return ret;
+}
+
+static void ext_cpld_gpio_irq_teardown(struct device *dev,
+				       struct ext_cpld_gpio *chip)
+{
+	struct pmb_boot_cpld_ext *cpld = chip->base;
+	int i;
+
+	if (chip->irq) {
+		iowrite16(0, &cpld->sib_presence_irq_en);
+		iowrite16(0, &cpld->sib_irq_en);
+	}
+
+	for (i = 0; i < EXT_CPLD_NGPIO; i++) {
+		int irq = irq_find_mapping(chip->domain, i);
+
+		if (irq > 0)
+			irq_dispose_mapping(irq);
+	}
+	irq_domain_remove(chip->domain);
+}
+
+static int ext_cpld_gpio_of_xlate(struct gpio_chip *gpio,
+				  const struct of_phandle_args *gpiospec,
+				  u32 *flags)
+{
+	if (WARN_ON(gpio->of_gpio_n_cells < 2))
+		return -EINVAL;
+
+	if (WARN_ON(gpiospec->args_count < gpio->of_gpio_n_cells))
+		return -EINVAL;
+
+	if (gpiospec->args[0] > gpio->ngpio)
+		return -EINVAL;
+
+	if (flags)
+		*flags = gpiospec->args[1] >> 16;
+
+	return gpiospec->args[0];
+}
+
+static void ext_cpld_gpio_setup(struct ext_cpld_gpio *chip)
+{
+	struct gpio_chip *gpio = &chip->gpio;
+
+	gpio->label = dev_name(chip->dev);
+	gpio->owner = THIS_MODULE;
+	gpio->get = ext_cpld_gpio_get;
+	gpio->direction_input = ext_cpld_gpio_direction_input;
+	gpio->dbg_show = NULL;
+	gpio->base = -1;
+	gpio->ngpio = EXT_CPLD_NGPIO;
+	gpio->can_sleep = 0;
+	gpio->of_node = chip->dev->of_node;
+	gpio->of_xlate = ext_cpld_gpio_of_xlate;
+	gpio->of_gpio_n_cells = 2;
+}
+
+static int ext_cpld_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct pmb_boot_cpld_ext *cpld;
+	struct ext_cpld_gpio *chip;
+	struct resource *res;
+	int ret;
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = dev;
+	platform_set_drvdata(pdev, chip);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	chip->base = devm_ioremap(dev, res->start, resource_size(res));
+	if (!chip->base)
+		return -ENOMEM;
+
+	cpld = chip->base;
+	chip->sib_presence_cache = ioread16(&cpld->sib_presence);
+
+	mutex_init(&chip->irq_lock);
+	mutex_init(&chip->work_lock);
+	ext_cpld_gpio_setup(chip);
+
+	ret = ext_cpld_gpio_irq_setup(dev, chip);
+	if (ret < 0)
+		return ret;
+
+	ret = gpiochip_add(&chip->gpio);
+	if (ret) {
+		dev_err(dev, "Extended CPLD gpio: Failed to register GPIO\n");
+		goto teardown;
+	}
+	return 0;
+
+teardown:
+	if (chip->domain)
+		ext_cpld_gpio_irq_teardown(dev, chip);
+	return ret;
+}
+
+static int ext_cpld_gpio_remove(struct platform_device *pdev)
+{
+	struct ext_cpld_gpio *chip = platform_get_drvdata(pdev);
+
+	cancel_delayed_work_sync(&chip->work);
+	if (chip->domain)
+		ext_cpld_gpio_irq_teardown(&pdev->dev, chip);
+
+	gpiochip_remove(&chip->gpio);
+
+	return 0;
+}
+
+static const struct of_device_id ext_cpld_gpio_ids[] = {
+	{ .compatible = "jnx,gpio-ptxpmb-ext-cpld", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, ext_cpld_gpio_ids);
+
+static struct platform_driver ext_cpld_gpio_driver = {
+	.driver = {
+		.name = "gpio-ptxpmb-ext-cpld",
+		.owner  = THIS_MODULE,
+		.of_match_table = ext_cpld_gpio_ids,
+	},
+	.probe = ext_cpld_gpio_probe,
+	.remove = ext_cpld_gpio_remove,
+};
+
+static int __init ext_cpld_gpio_init(void)
+{
+	return platform_driver_register(&ext_cpld_gpio_driver);
+}
+module_init(ext_cpld_gpio_init);
+
+static void __exit ext_cpld_gpio_exit(void)
+{
+	platform_driver_unregister(&ext_cpld_gpio_driver);
+}
+module_exit(ext_cpld_gpio_exit);
+
+MODULE_DESCRIPTION("Extended CPLD FPGA GPIO Driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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 related

* [PATCH 2/4] mfd: ptxpmb-ext-cpld: Add documentation for PTXPMB extended CPLD
From: Pantelis Antoniou @ 2016-10-07 15:19 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Georgi Vlaev, Guenter Roeck,
	JawaharBalaji Thirumalaisamy, Pantelis Antoniou, devicetree,
	linux-kernel, linux-gpio, linux-i2c, linux-mtd, linux-watchdog,
	netdev
In-Reply-To: <1475853574-22339-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add DT bindings document for the PTXPMB extended CPLD device.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../bindings/mfd/jnx-ptxpmb-ext-cpld.txt           | 35 ++++++++++++++++++++++
 1 file changed, 35 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-ptxpmb-ext-cpld.txt

diff --git a/Documentation/devicetree/bindings/mfd/jnx-ptxpmb-ext-cpld.txt b/Documentation/devicetree/bindings/mfd/jnx-ptxpmb-ext-cpld.txt
new file mode 100644
index 0000000..098a548a
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/jnx-ptxpmb-ext-cpld.txt
@@ -0,0 +1,35 @@
+* Device tree bindings for Juniper's PTXPMB Extended CPLD FPGA MFD driver
+
+The device supports a gpio block which is described in the
+jnx-gpio-ptxpmb-ext-cpld document.
+
+Required properties:
+
+- compatible:		"jnx,ptxpmb-ext-cpld"
+
+- reg:			contains offset/length value for device state control
+			registers space.
+
+Optional properties:
+
+- interrupts:		The interrupt line(s) the /IRQ signal(s) for the device is
+			connected to.
+
+- interrupt-parent:	The parent interrupt controller.
+
+Example:
+
+ext-cpld@1,0 {
+	compatible = "jnx,ptxpmb-ext-cpld";
+	reg = <0x1 0 0x1000>;
+	interrupt-parent = <&mpic>;
+	interrupts = <7 2>, <8 2>;
+
+	gpio_ext_cpld: cpld-ext-gpio {
+		compatible = "jnx,gpio-ptxpmb-ext-cpld";
+		#gpio-cells = <2>;
+		#interrupt-cells = <2>;
+		gpio-controller;
+		interrupt-controller;
+	};
+};
-- 
1.9.1

^ permalink raw reply related

* [PATCH 1/4] mfd: ptxpmb: Add separate driver for extended CPLD
From: Pantelis Antoniou @ 2016-10-07 15:19 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Georgi Vlaev, Guenter Roeck,
	JawaharBalaji Thirumalaisamy, Pantelis Antoniou,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-watchdog-u79uwXL29TY76Z2rM5mHXA,
	netdev-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1475853574-22339-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

Extended CPLD only exists on certain boards (SPMB) and by itself requires
an MFD driver, since it supports its own interrupts and sub-devices.
It also needs to provide support for SIB hotplug. It is cleaner and easier
to maintain it as separate driver.

Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: JawaharBalaji Thirumalaisamy <jawaharb-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
Signed-off-by: Tom Kavanagh <tkavanagh-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 drivers/mfd/Kconfig                 |  13 +++
 drivers/mfd/Makefile                |   1 +
 drivers/mfd/ptxpmb-ext-cpld-core.c  | 221 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/ptxpmb_ext_cpld.h |  42 +++++++
 4 files changed, 277 insertions(+)
 create mode 100644 drivers/mfd/ptxpmb-ext-cpld-core.c
 create mode 100644 include/linux/mfd/ptxpmb_ext_cpld.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 75b46a1..7e1fa14 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1371,6 +1371,19 @@ config MFD_JUNIPER_SAM
 	  This driver can be built as a module. If built as a module it will be
 	  called "sam-core"
 
+config MFD_JUNIPER_EXT_CPLD
+	tristate "Juniper PTX PMB Extended CPLD"
+	depends on PTXPMB_COMMON
+	default y if PTXPMB_COMMON
+	select MFD_CORE
+	help
+	  Select this to enable the PTX PMB Extended CPLD multi-function kernel
+	  driver for the applicable Juniper platforms.
+
+	  This driver can be built as a module. If built as a module it will be
+	  called "ptxpmb-ext-cpld"
+
+
 config MFD_TWL4030_AUDIO
 	bool "TI TWL4030 Audio"
 	depends on TWL4030_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 71a8ba6..da94482 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -150,6 +150,7 @@ obj-$(CONFIG_AB8500_DEBUG)	+= ab8500-debugfs.o
 obj-$(CONFIG_AB8500_GPADC)	+= ab8500-gpadc.o
 obj-$(CONFIG_MFD_JUNIPER_CPLD)	+= ptxpmb-cpld-core.o
 obj-$(CONFIG_MFD_JUNIPER_SAM)	+= sam-core.o
+obj-$(CONFIG_MFD_JUNIPER_EXT_CPLD) += ptxpmb-ext-cpld-core.o
 obj-$(CONFIG_MFD_DB8500_PRCMU)	+= db8500-prcmu.o
 # ab8500-core need to come after db8500-prcmu (which provides the channel)
 obj-$(CONFIG_AB8500_CORE)	+= ab8500-core.o ab8500-sysctrl.o
diff --git a/drivers/mfd/ptxpmb-ext-cpld-core.c b/drivers/mfd/ptxpmb-ext-cpld-core.c
new file mode 100644
index 0000000..a1b1793
--- /dev/null
+++ b/drivers/mfd/ptxpmb-ext-cpld-core.c
@@ -0,0 +1,221 @@
+/*
+ * Juniper PTX PMB Extended CPLD multi-function core driver
+ *
+ * Copyright (C) 2012 Juniper Networks
+ *
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/of.h>
+#include <linux/sched.h>
+#include <linux/mfd/ptxpmb_ext_cpld.h>
+#include <linux/jnx/jnx-subsys.h>
+
+struct pmb_ext_cpld_core {
+	struct device		*dev;
+	struct pmb_boot_cpld_ext __iomem *cpld;
+	spinlock_t		lock;
+	int			irq0, irq1;
+	wait_queue_head_t	wqh;
+};
+
+static irqreturn_t pmb_ext_cpld_core_interrupt(int irq, void *dev_data)
+{
+	struct pmb_ext_cpld_core *cpld = dev_data;
+	unsigned char __iomem *data;
+	int i;
+	u8 buffer[48];
+
+	dev_info(cpld->dev, "interrupt %d sib presence=0x%x irq status=0x%x\n",
+		 irq, ioread16(&cpld->cpld->sib_presence),
+		 ioread16(&cpld->cpld->sib_irq_status));
+
+	data = (u8 *)cpld->cpld;
+	for (i = 0; i < 48; i++)
+		buffer[i] = ioread8(data + i);
+
+	print_hex_dump(KERN_INFO, dev_name(cpld->dev), DUMP_PREFIX_OFFSET,
+		       16, 4, buffer, 48, false);
+
+	spin_lock(&cpld->wqh.lock);
+
+	/* clear interrupt, wake up any handlers */
+	wake_up_locked(&cpld->wqh);
+
+	spin_unlock(&cpld->wqh.lock);
+
+	return IRQ_HANDLED;
+}
+
+static ssize_t show_sib_status(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct pmb_ext_cpld_core *cpld = dev_get_drvdata(dev);
+
+	WARN_ONCE(1,
+		  "sib_status is deprecated and should no longer be used for presence detection\n");
+
+	return sprintf(buf, "0x%04x\n",
+		       ioread16(&cpld->cpld->sib_presence));
+}
+
+static DEVICE_ATTR(sib_status, S_IRUGO, show_sib_status, NULL);
+
+static struct resource pmb_ext_cpld_resources[] = {
+	{
+		.start	= 0,
+		.end	= sizeof(struct pmb_boot_cpld_ext) - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+};
+
+static struct mfd_cell pmb_ext_cpld_cells[] = {
+	{
+		.name = "gpio-ptxpmb-ext-cpld",
+		.num_resources = ARRAY_SIZE(pmb_ext_cpld_resources),
+		.resources = pmb_ext_cpld_resources,
+		.of_compatible = "jnx,gpio-ptxpmb-ext-cpld",
+	},
+};
+
+static int pmb_ext_cpld_core_probe(struct platform_device *pdev)
+{
+	static struct pmb_ext_cpld_core *cpld;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	int error;
+	u16 asid;
+
+	cpld = devm_kzalloc(dev, sizeof(*cpld), GFP_KERNEL);
+	if (!cpld)
+		return -ENOMEM;
+
+	cpld->dev = dev;
+	dev_set_drvdata(dev, cpld);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	cpld->cpld = devm_ioremap_resource(dev, res);
+	if (IS_ERR(cpld->cpld))
+		return PTR_ERR(cpld->cpld);
+
+	cpld->irq0 = platform_get_irq(pdev, 0);
+	if (cpld->irq0 >= 0) {
+		error = devm_request_threaded_irq(dev, cpld->irq0, NULL,
+						  pmb_ext_cpld_core_interrupt,
+						  IRQF_TRIGGER_RISING |
+						  IRQF_TRIGGER_FALLING |
+						  IRQF_ONESHOT,
+						  dev_name(dev), cpld);
+		if (error < 0)
+			return error;
+	}
+
+	cpld->irq1 = platform_get_irq(pdev, 1);
+	if (cpld->irq1 >= 0) {
+		error = devm_request_threaded_irq(dev, cpld->irq1, NULL,
+						  pmb_ext_cpld_core_interrupt,
+						  IRQF_TRIGGER_RISING |
+						  IRQF_TRIGGER_FALLING |
+						  IRQF_ONESHOT,
+						  dev_name(dev), cpld);
+		if (error < 0)
+			return error;
+	}
+
+	spin_lock_init(&cpld->lock);
+	init_waitqueue_head(&cpld->wqh);
+
+	asid = ioread16be(&cpld->cpld->cb_assembly_id);
+
+	dev_info(dev, "Ext CPLD rev %u.%u CB asy 0x%x CB rev %u.%u\n",
+		 ioread8(&cpld->cpld->major_rev),
+		 ioread8(&cpld->cpld->minor_rev),
+		 asid,
+		 ioread8(&cpld->cpld->cb_major_version),
+		 ioread8(&cpld->cpld->cb_minor_version));
+
+	dev_info(dev, "SIB status=0x%x SIB irq status=0x%x\n",
+		 ioread16(&cpld->cpld->sib_presence),
+		 ioread16(&cpld->cpld->sib_irq_status));
+
+	error = device_create_file(dev, &dev_attr_sib_status);
+	if (error)
+		return error;
+	error = mfd_add_devices(dev, pdev->id, pmb_ext_cpld_cells,
+				ARRAY_SIZE(pmb_ext_cpld_cells), res,
+				0, NULL);
+	if (error < 0)
+		goto abort;
+
+	iowrite16(0xffff, &cpld->cpld->sib_presence_irq_en);
+	iowrite16(0xffff, &cpld->cpld->sib_irq_en);
+
+	return 0;
+
+abort:
+	device_remove_file(dev, &dev_attr_sib_status);
+	return error;
+}
+
+static int pmb_ext_cpld_core_remove(struct platform_device *pdev)
+{
+	mfd_remove_devices(&pdev->dev);
+	device_remove_file(&pdev->dev, &dev_attr_sib_status);
+	return 0;
+}
+
+static const struct of_device_id pmb_ext_cpld_of_ids[] = {
+	{ .compatible = "jnx,ptxpmb-ext-cpld" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pmb_ext_cpld_of_ids);
+
+static struct platform_driver pmb_ext_cpld_core_driver = {
+	.probe		= pmb_ext_cpld_core_probe,
+	.remove		= pmb_ext_cpld_core_remove,
+	.driver		= {
+		.name	= "ptxpmb-ext-cpld",
+		.of_match_table = pmb_ext_cpld_of_ids,
+		.owner	= THIS_MODULE,
+	}
+};
+
+static int __init pmb_ext_cpld_core_init(void)
+{
+	return platform_driver_register(&pmb_ext_cpld_core_driver);
+}
+module_init(pmb_ext_cpld_core_init);
+
+static void __exit pmb_ext_cpld_core_exit(void)
+{
+	platform_driver_unregister(&pmb_ext_cpld_core_driver);
+}
+module_exit(pmb_ext_cpld_core_exit);
+
+MODULE_DESCRIPTION("Juniper PTX PMB Extended CPLD Core Driver");
+MODULE_AUTHOR("Guenter Roeck <groeck-3r7Miqu9kMnR7s880joybQ@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ptxpmb-cpld");
diff --git a/include/linux/mfd/ptxpmb_ext_cpld.h b/include/linux/mfd/ptxpmb_ext_cpld.h
new file mode 100644
index 0000000..bac9a73
--- /dev/null
+++ b/include/linux/mfd/ptxpmb_ext_cpld.h
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------
+ *
+ * ptxpmb_ext_cpld_core.h
+ *     Copyright (c) 2012, 2013 Juniper Networks
+ *
+ *---------------------------------------------------------------------------
+ */
+
+#ifndef PTXPMB_EXT_CPLD_CORE_H
+#define PTXPMB_EXT_CPLD_CORE_H
+
+/*
+ * Extended CPLD registers
+ */
+struct pmb_boot_cpld_ext {
+	u8 major_rev;			/* 0x00 */
+	u8 minor_rev;			/* 0x01 */
+	u16 sib_presence;		/* 0x02 */
+	u8 unused0[4];			/* 0x04 */
+	u16 sib_presence_irq_en;	/* 0x08 */
+	u8 unused1[4];			/* 0x0a */
+	u16 sib_irq_status;		/* 0x0e */
+	u8 unused2[4];			/* 0x10 */
+	u16 sib_irq_en;			/* 0x14 */
+	u8 unused3[4];			/* 0x16 */
+	u8 i2c_group;			/* 0x1a */
+	u8 i2c_mux_sel;			/* 0x1b */
+	u8 re_status;			/* 0x1c */
+	u8 unused4[0x24 - 0x1d];	/* 0x1d */
+	u16 cb_assembly_id;		/* 0x24 */
+	u8 cb_major_version;		/* 0x26 */
+	u8 cb_minor_version;		/* 0x27 */
+	u8 unused5[0x40 - 0x28];	/* 0x28 */
+	u8 spare_a;			/* 0x40 */
+	u8 spare_b;			/* 0x41 */
+	u8 spare_c;			/* 0x42 */
+	u8 spare_d;			/* 0x43 */
+	u8 spare_e;			/* 0x44 */
+	u8 unused6[0x3ff - 0x45];	/* 0x45 */
+};
+
+#endif /* PTXPMB_EXT_CPLD_CORE_H */
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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 related

* [PATCH 0/4] Introduce Juniper PTXPMB Extended driver
From: Pantelis Antoniou @ 2016-10-07 15:19 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Georgi Vlaev, Guenter Roeck,
	JawaharBalaji Thirumalaisamy, Pantelis Antoniou, devicetree,
	linux-kernel, linux-gpio, linux-i2c, linux-mtd, linux-watchdog,
	netdev

Add Juniper's PTXPMB Extended FPGA driver. Those FPGAs
are present in Juniper's PTX series of routers.

The MFD driver provices a gpio device.

There are full device tree binding documents for the
master mfd driver and for the slave driver.

This patchset is against mainline as of today: v4.8-9431-g3477d16
and is dependent on the "Juniper prerequisites" and
"Juniper infrastructure" patchsets sent earlier.

Georgi Vlaev (2):
  mfd: ptxpmb-ext-cpld: Add documentation for PTXPMB extended CPLD
  gpio: ptxpmb-ext-cpld: Document bindings of PTXPMB extended CPLD

Guenter Roeck (2):
  mfd: ptxpmb: Add separate driver for extended CPLD
  gpio: ptxpmb-ext-cpld: Add driver for Juniper's PTXPMB extended CPLD

 .../bindings/gpio/jnx,gpio-ptxpmb-ext-cpld.txt     |  36 ++
 .../bindings/mfd/jnx-ptxpmb-ext-cpld.txt           |  35 ++
 drivers/gpio/Kconfig                               |  11 +
 drivers/gpio/Makefile                              |   1 +
 drivers/gpio/gpio-ptxpmb-ext-cpld.c                | 430 +++++++++++++++++++++
 drivers/mfd/Kconfig                                |  13 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/ptxpmb-ext-cpld-core.c                 | 221 +++++++++++
 include/linux/mfd/ptxpmb_ext_cpld.h                |  42 ++
 9 files changed, 790 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-ptxpmb-ext-cpld.txt
 create mode 100644 Documentation/devicetree/bindings/mfd/jnx-ptxpmb-ext-cpld.txt
 create mode 100644 drivers/gpio/gpio-ptxpmb-ext-cpld.c
 create mode 100644 drivers/mfd/ptxpmb-ext-cpld-core.c
 create mode 100644 include/linux/mfd/ptxpmb_ext_cpld.h

-- 
1.9.1


^ permalink raw reply

* [PATCH 10/10] net: mdio-sam: Add device tree documentation for SAM MDIO
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>

From: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>

Add device tree bindings document for the SAM MDIO block
present in Juniper's SAM FPGA.

Signed-off-by: Georgi Vlaev <gvlaev-3r7Miqu9kMnR7s880joybQ@public.gmane.org>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou-OWPKS81ov/FWk0Htik3J/w@public.gmane.org>
---
 Documentation/devicetree/bindings/net/mdio-sam.txt | 48 ++++++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/mdio-sam.txt

diff --git a/Documentation/devicetree/bindings/net/mdio-sam.txt b/Documentation/devicetree/bindings/net/mdio-sam.txt
new file mode 100644
index 0000000..7d354e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/mdio-sam.txt
@@ -0,0 +1,48 @@
+Juniper SAM FPGA MFD MDIO bus properties.
+
+Required properties:
+- compatible : "jnx,mdio-sam"
+- reg : The start offset of the MDIO bus range
+- #address-cells = <1>;
+- #size-cells = <0>;
+
+Optional properties:
+
+Required properties for child nodes:
+- #address-cells = <1>;
+- #size-cells = <0>;
+- reg : The MDIO bus offset within the MDIO range.
+
+
+Example :
+
+	sam@10 {
+		compatible = "jnx,sam";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		mdio-sam@10 {
+			compatible = "jnx,mdio-sam";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0x40000>;
+
+			mdio0: mdio-sam@0 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <0x0>;
+			};
+
+			mdio1: mdio-sam@4000 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <0x4000>;
+			};
+
+			mdio2: mdio-sam@8000 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <0x8000>;
+			};
+		};
+	};
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" 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 related

* [PATCH 09/10] net: phy: Add MDIO driver for Juniper's SAM FPGA
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add driver for the MDIO IP block present in Juniper's
SAM FPGA.

This driver supports only Clause 45 of the 802.3 spec.

Note that due to the fact that there are no drivers for
Broadcom/Avago retimers on 10/40Ge path that are controlled
from the MDIO interface there is a method to have direct
access to registers via a debugfs interface.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/net/phy/Kconfig    |   8 +
 drivers/net/phy/Makefile   |   1 +
 drivers/net/phy/mdio-sam.c | 564 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 573 insertions(+)
 create mode 100644 drivers/net/phy/mdio-sam.c

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 5078a0d..7d7f265 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -122,6 +122,14 @@ config MDIO_OCTEON
 	  buses. It is required by the Octeon and ThunderX ethernet device
 	  drivers on some systems.
 
+config MDIO_SAM
+	tristate "Juniper Networks SAM FPGA MDIO controller"
+	depends on MFD_JUNIPER_SAM
+	help
+	  This module provides a driver for the Juniper Network SAM FPGA MDIO
+	  buses. This hardware can be found in the Gladiator PIC SAM FPGA. This
+	  driver is client of the sam-core MFD driver.
+
 config MDIO_SUN4I
 	tristate "Allwinner sun4i MDIO interface support"
 	depends on ARCH_SUNXI
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e58667d..c7631cf 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_MDIO_GPIO)		+= mdio-gpio.o
 obj-$(CONFIG_MDIO_HISI_FEMAC)	+= mdio-hisi-femac.o
 obj-$(CONFIG_MDIO_MOXART)	+= mdio-moxart.o
 obj-$(CONFIG_MDIO_OCTEON)	+= mdio-octeon.o
+obj-$(CONFIG_MDIO_SAM)		+= mdio-sam.o
 obj-$(CONFIG_MDIO_SUN4I)	+= mdio-sun4i.o
 obj-$(CONFIG_MDIO_THUNDER)	+= mdio-thunder.o
 obj-$(CONFIG_MDIO_XGENE)	+= mdio-xgene.o
diff --git a/drivers/net/phy/mdio-sam.c b/drivers/net/phy/mdio-sam.c
new file mode 100644
index 0000000..73cefa1
--- /dev/null
+++ b/drivers/net/phy/mdio-sam.c
@@ -0,0 +1,564 @@
+/*
+ * Juniper Networks SAM FPGA MDIO driver.
+ *
+ * Copyright (c) 2015, Juniper Networks
+ * Author: Georgi Vlaev <gvlaev@juniper.net>
+ *
+ * The MDIO bus driver supports GPQAM, GPCAM, GPQ28 FPGAs found
+ * on Juniper's 10/40/100GE Gladiator PIC cards. Only Clause 45
+ * access is currently available natively.
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ */
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#endif
+
+#define MDIO_CMD1	0x0000 /* Command Table 1 */
+#define MDIO_CMD2	0x0800 /* Command Table 2 */
+#define MDIO_RESULT	0x1000 /* Result Table (RO) */
+#define MDIO_PRI_CMD1	0x1800 /* Priority Command Table 1 */
+#define MDIO_PRI_CMD2	0x2000 /* Priority Command Table 1 */
+#define MDIO_PRI_RESULT	0x2800 /* Priority Result Table (RO) */
+#define MDIO_TBL_CMD	0x3000 /* Table Command Register (WO) */
+#define MDIO_STATUS	0x3008 /* Master Status (RO) */
+#define MDIO_STATUS_INT	0x3010 /* Master Status Interrupt Mask (W1C) */
+
+/* MDIO_TBL_CMD */
+#define TBL_CMD_REG_ABORT	BIT(31) /* Regular Table ABORT */
+#define TBL_CMD_REG_GO		BIT(30) /* Regular Table GO */
+#define TBL_CMD_PRI_ABORT	BIT(29) /* Priority Table Abort */
+#define TBL_CMD_PRI_GO		BIT(28) /* Priority Table GO */
+#define TBL_CMD_SOFT_RESET	BIT(27) /* Soft Reset */
+
+/* MDIO_STATUS */
+#define STAT_REG_RDY	BIT(31) /* READY for Programming Regular Table */
+#define STAT_REG_DONE	BIT(30)	/* DONE SUCCESSFULLY WITH REGULAR TABLE */
+#define STAT_PRI_RDY	BIT(29) /* READY for Programming Priority Table */
+#define STAT_PRI_DONE	BIT(28) /* DONE SUCCESSFULLY WITH PRIORITY TABLE */
+#define STAT_REG_ERR	BIT(27) /* DONE WITH ERRORS for Regular Table */
+#define STAT_PRI_ERR	BIT(26) /* DONE WITH ERRORS for Priority Table */
+#define STAT_REG_PROG_ERR	BIT(25) /* Programming Err for Regular Table */
+#define STAT_PRI_PROG_ERR	BIT(24) /* Programming Err for Priority Table */
+
+/* MDIO_CMD2, MDIO_PRI_CMD2 */
+#define CMD2_ENABLE	BIT(17)
+#define CMD2_READ	BIT(16)
+
+/* MDIO_RESULT, MDIO_PRI_RESULT */
+#define RES_SUCCESS	BIT(17)
+#define RES_ERROR	BIT(16)
+
+#define MDIO_RDY_TMO	30 /* in msec */
+
+struct mdio_sam_data {
+	void __iomem *base;
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *dir;
+	/* Clause 45 addressing
+	 * addr[5]: PHYAD
+	 * reg[21]: DEVAD 5 bits + REG 16 bits
+	 * value[16]
+	 */
+	u8 addr;	/* phyad */
+	u32 reg;	/* devad + reg = (devad & 0x1f) << 16 | reg */
+	u16 value;	/* value */
+#endif
+};
+
+/* raw_io binary attribute: read/write any device on the bus */
+static ssize_t
+mdio_sam_sysfs_write_raw_io(struct file *filp,
+			    struct kobject *kobj,
+			    struct bin_attribute *attr,
+			    char *buf, loff_t offset, size_t size)
+{
+	struct mii_bus *bus = to_mii_bus(container_of(kobj,
+					struct device, kobj));
+	int ret;
+	u32 reg = offset & 0x1fffff;
+	u8 phy = (offset >> 21) & 0x1f;
+
+	if (size != 2)
+		return -EINVAL;
+
+	ret = mdiobus_write(bus, phy, reg | MII_ADDR_C45, *(u16 *)buf);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t
+mdio_sam_sysfs_read_raw_io(struct file *filp,
+			   struct kobject *kobj,
+			   struct bin_attribute *attr,
+			   char *buf, loff_t offset, size_t size)
+{
+	struct mii_bus *bus = to_mii_bus(container_of(kobj,
+					struct device, kobj));
+	int ret;
+	u32 reg = offset & 0x1fffff;
+	u8 phy = (offset >> 21) & 0x1f;
+
+	if (size != 2)
+		return -EINVAL;
+
+	ret = mdiobus_read(bus, phy, reg | MII_ADDR_C45);
+	if (ret < 0)
+		return ret;
+
+	*(u16 *)buf = (u16)ret;
+
+	return size;
+}
+
+static struct bin_attribute bin_attr_raw_io = {
+	.attr = {.name = "raw_io", .mode = (S_IRUGO | S_IWUSR)},
+	.size = 0x4000000,
+	.read = mdio_sam_sysfs_read_raw_io,
+	.write = mdio_sam_sysfs_write_raw_io,
+};
+
+#ifdef CONFIG_DEBUG_FS
+/* debugfs: set/get register offset */
+static int mdio_sam_debugfs_addr_print(struct seq_file *s, void *p)
+{
+	struct mdio_sam_data *data = (struct mdio_sam_data *)s->private;
+
+	seq_printf(s, "0x%02x\n", data->addr);
+
+	return 0;
+}
+
+static int mdio_sam_debugfs_addr_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mdio_sam_debugfs_addr_print, inode->i_private);
+}
+
+static ssize_t
+mdio_sam_debugfs_addr_write(struct file *file, const char __user *user_buf,
+			    size_t count, loff_t *ppos)
+{
+	struct mdio_sam_data *data =
+		((struct seq_file *)(file->private_data))->private;
+	unsigned long addr;
+	int err;
+
+	err = kstrtoul_from_user(user_buf, count, 0, &addr);
+	if (err)
+		return err;
+
+	if (addr > 0x1f)
+		return -EINVAL;
+
+	data->addr = (u8)(addr);
+
+	return count;
+}
+
+static const struct file_operations mdio_sam_debugfs_addr_fops = {
+	.open = mdio_sam_debugfs_addr_open,
+	.write = mdio_sam_debugfs_addr_write,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.owner = THIS_MODULE,
+};
+
+/* debugfs: set/get register offset */
+static int mdio_sam_debugfs_reg_print(struct seq_file *s, void *p)
+{
+	struct mdio_sam_data *data = (struct mdio_sam_data *)s->private;
+
+	seq_printf(s, "0x%06X\n", data->reg);
+
+	return 0;
+}
+
+static int mdio_sam_debugfs_reg_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mdio_sam_debugfs_reg_print, inode->i_private);
+}
+
+static ssize_t
+mdio_sam_debugfs_reg_write(struct file *file, const char __user *user_buf,
+			   size_t count, loff_t *ppos)
+{
+	struct mdio_sam_data *data =
+		((struct seq_file *)(file->private_data))->private;
+	unsigned long reg;
+	int err;
+
+	err = kstrtoul_from_user(user_buf, count, 0, &reg);
+	if (err)
+		return err;
+
+	if (reg > 0x1fffff)
+		return -EINVAL;
+
+	data->reg = reg;
+
+	return count;
+}
+
+static const struct file_operations mdio_sam_debugfs_reg_fops = {
+	.open = mdio_sam_debugfs_reg_open,
+	.write = mdio_sam_debugfs_reg_write,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.owner = THIS_MODULE,
+};
+
+/* debugfs: set/get register value */
+static int mdio_sam_debugfs_val_print(struct seq_file *s, void *p)
+{
+	struct mii_bus *bus = (struct mii_bus *)s->private;
+	struct mdio_sam_data *data = bus->priv;
+	int ret;
+
+	ret = mdiobus_read(bus, data->addr, data->reg | MII_ADDR_C45);
+	if (ret < 0)
+		return ret;
+
+	seq_printf(s, "0x%04X\n", ret);
+	return 0;
+}
+
+static int mdio_sam_debugfs_val_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, mdio_sam_debugfs_val_print, inode->i_private);
+}
+
+static ssize_t
+mdio_sam_debugfs_val_write(struct file *file, const char __user *user_buf,
+			   size_t count, loff_t *ppos)
+{
+	struct mii_bus *bus =
+		((struct seq_file *)(file->private_data))->private;
+	struct mdio_sam_data *data = bus->priv;
+	unsigned long value;
+	int ret;
+
+	ret = kstrtoul_from_user(user_buf, count, 0, &value);
+	if (ret)
+		return ret;
+
+	ret = mdiobus_write(bus, data->addr, data->reg | MII_ADDR_C45, value);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static const struct file_operations mdio_sam_debugfs_val_fops = {
+	.open = mdio_sam_debugfs_val_open,
+	.write = mdio_sam_debugfs_val_write,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.owner = THIS_MODULE,
+};
+
+static int mdio_sam_debugfs_init(struct mii_bus *bus)
+{
+	struct dentry *file;
+	struct mdio_sam_data *data = bus->priv;
+
+	data->dir = debugfs_create_dir(bus->id, NULL);
+	if (!data->dir)
+		return -ENOMEM;
+
+/* phy */
+	file = debugfs_create_file("addr", (S_IRUGO | S_IWUSR),
+				   data->dir, data,
+				   &mdio_sam_debugfs_addr_fops);
+	if (!file)
+		goto err;
+
+/* reg */
+	file = debugfs_create_file("reg", (S_IRUGO | S_IWUSR),
+				   data->dir, data,
+				   &mdio_sam_debugfs_reg_fops);
+	if (!file)
+		goto err;
+
+/* value */
+	file = debugfs_create_file("value", (S_IRUGO | S_IWUSR),
+				   data->dir, bus,
+				   &mdio_sam_debugfs_val_fops);
+	if (!file)
+		goto err;
+
+	return 0;
+err:
+	debugfs_remove_recursive(data->dir);
+	dev_err(&bus->dev, "failed to create debugfs entries.\n");
+
+	return -ENOMEM;
+}
+
+static void mdio_sam_debugfs_remove(struct mii_bus *bus)
+{
+	struct mdio_sam_data *data = bus->priv;
+
+	debugfs_remove_recursive(data->dir);
+}
+#endif
+
+static int mdio_sam_stat_wait(struct mii_bus *bus, u32 wait_mask)
+{
+	struct mdio_sam_data *data = bus->priv;
+	unsigned long timeout;
+	u32 stat;
+
+	timeout = jiffies + msecs_to_jiffies(MDIO_RDY_TMO);
+	do {
+		stat = ioread32(data->base + MDIO_STATUS);
+		if (stat & wait_mask)
+			return 0;
+
+		usleep_range(50, 100);
+	} while (time_before(jiffies, timeout));
+
+	return -EBUSY;
+}
+
+static int mdio_sam_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+	struct mdio_sam_data *data = bus->priv;
+	u32 command, res;
+	int ret;
+
+	/* mdiobus_read holds the bus->mdio_lock mutex */
+
+	if (!(regnum & MII_ADDR_C45))
+		return -ENXIO;
+
+	ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
+	if (ret < 0)
+		return ret;
+
+	command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
+	command |= ((phy_id & 0x1f) << 21);
+
+	iowrite32(command, data->base + MDIO_CMD1);
+	ioread32(data->base + MDIO_CMD1);
+	iowrite32(CMD2_READ | CMD2_ENABLE, data->base + MDIO_CMD2);
+	ioread32(data->base + MDIO_CMD2);
+	iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
+	ioread32(data->base + MDIO_TBL_CMD);
+
+	usleep_range(50, 100);
+
+	ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
+	if (ret < 0)
+		return ret;
+
+	res = ioread32(data->base + MDIO_RESULT);
+
+	if (res & RES_ERROR || !(res & RES_SUCCESS))
+		return -EIO;
+
+	return (res & 0xffff);
+}
+
+static int mdio_sam_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
+{
+	struct mdio_sam_data *data = bus->priv;
+	u32 command;
+	int ret;
+
+	/* mdiobus_write holds the bus->mdio_lock mutex */
+
+	if (!(regnum & MII_ADDR_C45))
+		return -ENXIO;
+
+	ret = mdio_sam_stat_wait(bus, STAT_REG_RDY);
+	if (ret < 0)
+		return ret;
+
+	command = regnum & 0x1fffff; /* regnum = (dev_id << 16) | reg */
+	command |= ((phy_id & 0x1f) << 21);
+
+	iowrite32(command, data->base + MDIO_CMD1);
+	ioread32(data->base + MDIO_CMD1);
+	iowrite32(CMD2_ENABLE | val, data->base + MDIO_CMD2);
+	ioread32(data->base + MDIO_CMD2);
+	iowrite32(TBL_CMD_REG_GO, data->base + MDIO_TBL_CMD);
+	ioread32(data->base + MDIO_TBL_CMD);
+
+	usleep_range(50, 100);
+
+	ret = mdio_sam_stat_wait(bus, (STAT_REG_DONE | STAT_REG_ERR));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int mdio_sam_reset(struct mii_bus *bus)
+{
+	struct mdio_sam_data *data = bus->priv;
+
+	iowrite32(TBL_CMD_SOFT_RESET, data->base + MDIO_TBL_CMD);
+	ioread32(data->base + MDIO_TBL_CMD);
+	mdelay(10);
+	iowrite32(0, data->base + MDIO_TBL_CMD);
+	ioread32(data->base + MDIO_TBL_CMD);
+
+	/* zero tables */
+	memset_io(data->base + MDIO_CMD1, 0, 0x1000);
+	memset_io(data->base + MDIO_PRI_CMD1, 0, 0x1000);
+
+	return 0;
+}
+
+static int mdio_sam_of_register_bus(struct platform_device *pdev,
+				    struct device_node *np, void __iomem *base)
+{
+	struct mii_bus *bus;
+	struct mdio_sam_data *data;
+	u32 reg;
+	int ret;
+
+	bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*data));
+	if (!bus)
+		return -ENOMEM;
+
+	/* bus offset */
+	ret = of_property_read_u32(np, "reg", &reg);
+	if (ret)
+		return -ENODEV;
+
+	data = bus->priv;
+	data->base = base + reg;
+
+	bus->parent = &pdev->dev;
+	bus->name = "mdio-sam";
+	bus->read = mdio_sam_read;
+	bus->write = mdio_sam_write;
+	bus->reset = mdio_sam_reset;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "mdiosam-%x-%x", pdev->id, reg);
+
+	ret = of_mdiobus_register(bus, np);
+	if (ret < 0)
+		return ret;
+#ifdef CONFIG_DEBUG_FS
+	ret = mdio_sam_debugfs_init(bus);
+	if (ret < 0)
+		goto err_unregister;
+#endif
+	ret = device_create_bin_file(&bus->dev, &bin_attr_raw_io);
+	if (ret)
+		goto err_debugfs;
+
+	return 0;
+
+err_debugfs:
+#ifdef CONFIG_DEBUG_FS
+	mdio_sam_debugfs_remove(bus);
+#endif
+err_unregister:
+	mdiobus_unregister(bus);
+
+	return ret;
+}
+
+static int mdio_sam_of_unregister_bus(struct device_node *np)
+{
+	struct mii_bus *bus;
+
+	bus = of_mdio_find_bus(np);
+	if (bus) {
+		device_remove_bin_file(&bus->dev, &bin_attr_raw_io);
+#ifdef CONFIG_DEBUG_FS
+		mdio_sam_debugfs_remove(bus);
+#endif
+		mdiobus_unregister(bus);
+	}
+	return 0;
+}
+
+static int mdio_sam_probe(struct platform_device *pdev)
+{
+	struct device_node *np;
+	struct resource *res;
+	void __iomem *base;
+	int ret;
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_nocache(&pdev->dev, res->start,
+				    resource_size(res));
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	for_each_available_child_of_node(pdev->dev.of_node, np) {
+		ret = mdio_sam_of_register_bus(pdev, np, base);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+err:
+	/* roll back everything */
+	for_each_available_child_of_node(pdev->dev.of_node, np)
+		mdio_sam_of_unregister_bus(np);
+
+	return ret;
+}
+
+static int mdio_sam_remove(struct platform_device *pdev)
+{
+	struct device_node *np;
+
+	for_each_available_child_of_node(pdev->dev.of_node, np)
+		mdio_sam_of_unregister_bus(np);
+
+	return 0;
+}
+
+static const struct of_device_id mdio_sam_of_match[] = {
+	{ .compatible = "jnx,mdio-sam" },
+	{  }
+};
+MODULE_DEVICE_TABLE(of, mdio_sam_of_match);
+
+static struct platform_driver mdio_sam_driver = {
+	.probe = mdio_sam_probe,
+	.remove = mdio_sam_remove,
+	.driver = {
+		.name = "mdio-sam",
+		.owner = THIS_MODULE,
+		.of_match_table = mdio_sam_of_match,
+	},
+};
+
+module_platform_driver(mdio_sam_driver);
+
+MODULE_ALIAS("platform:mdio-sam");
+MODULE_AUTHOR("Georgi Vlaev <gvlaev@juniper.net>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Juniper Networks SAM MDIO bus driver");
-- 
1.9.1

^ permalink raw reply related

* [PATCH 08/10] mtd: flash-sam: Bindings for Juniper's SAM FPGA flash
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add binding document for Junipers Flash IP block present
in the SAM FPGA on PTX series of routers.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/mtd/flash-sam.txt          | 31 ++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mtd/flash-sam.txt

diff --git a/Documentation/devicetree/bindings/mtd/flash-sam.txt b/Documentation/devicetree/bindings/mtd/flash-sam.txt
new file mode 100644
index 0000000..bdf1d78
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/flash-sam.txt
@@ -0,0 +1,31 @@
+Flash device on a Juniper SAM FPGA
+
+These flash chips are found in the PTX series of Juniper routers.
+
+They are regular CFI compatible (Intel or AMD extended) flash chips with
+some special write protect/VPP bits that can be controlled by the machine's
+system controller.
+
+Required properties:
+- compatible : must be "jnx,flash-sam"
+
+Optional properties:
+- reg : memory address for the flash chip, note that this is not
+required since usually the device is a subdevice of the SAM MFD
+driver which fills in the register fields.
+
+For the rest of the properties, see mtd-physmap.txt.
+
+The device tree may optionally contain sub-nodes describing partitions of the
+address space. See partition.txt for more detail.
+
+Example:
+
+flash_sam {
+	compatible = "jnx,flash-sam";
+	partition@0 {
+		reg = <0x0 0x400000>;
+		label = "pic0-golden";
+		read-only;
+	};
+};
-- 
1.9.1


^ permalink raw reply related

* [PATCH 06/10] gpio: sam: Document bindings of SAM FPGA GPIO block
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add device tree bindings document for the GPIO driver of
Juniper's SAM FPGA.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/gpio/jnx,gpio-sam.txt      | 110 +++++++++++++++++++++
 1 file changed, 110 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt

diff --git a/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
new file mode 100644
index 0000000..514c350
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/jnx,gpio-sam.txt
@@ -0,0 +1,110 @@
+Juniper SAM FPGA GPIO block
+
+The controller's registers are organized as sets of eight 32-bit
+registers with each set controlling a bank of up to 32 pins.  A single
+interrupt is shared for all of the banks handled by the controller.
+
+Required properties:
+
+- compatible:
+    Must be "jnx,gpio-sam"
+
+- #gpio-cells:
+    Should be <2>.  The first cell is the pin number (within the controller's
+    pin space), and the second is used for the following flags:
+	bit[0]: direction (0 = out, 1 = in)
+	bit[1]: init high
+	bit[2]: active low
+	bit[3]: open drain
+	bit[4]: open drain
+
+- gpio-controller:
+    Specifies that the node is a GPIO controller.
+
+Optional properties:
+
+- reg:
+    This driver is part of the SAM FPGA MFD driver, so the
+    address range is supplied by that driver. However you can
+    override using this property.
+
+- gpio-base:
+    Base of the GPIO pins of this instance. If not present use system allocated.
+
+- gpio-count:
+    Number of GPIO pins of this instance. If not present read the number from
+    the one configured in the FPGA data. Maximum number is 512.
+
+- #interrupt-cells:
+    Should be <2>.  The first cell is the GPIO number, the second should specify
+    flags.  The following subset of flags is supported:
+    - bits[16,4:0] trigger type and level flags
+	bit  0: rising edge interrupt
+	bit  1: falling edge interrupt
+	bit  2: active high interrupt
+	bit  3: active low interrupt
+	bit  4: enable debounce
+	bit 16: signal is active low
+    See also Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
+
+- gpio-interrupts:
+    A number of triples that define the mapping of interrupt groupsb to a range of
+    pins. The first cell defines the interrupt group, the second is the start of
+    the pin range and the third the number of pins in the range.
+
+- gpio-exports:
+    A subnode containing the list of pins that will be exported to user-space.
+    Each subnode contains:
+    Required properties:
+	- pin: The gpio to be exported and the relevant flags.
+    Optional properties:
+        - label: The label to use for export; if not supplied use the node name.
+
+Example:
+
+gpio20: gpio-sam {
+	compatible = "jnx,gpio-sam";
+	gpio-controller;
+	interrupt-controller;
+	/* 1st cell: gpio pin
+	 * 2nd cell: flags (bit mask)
+	 * bit  0: rising edge interrupt
+	 * bit  1: falling edge interrupt
+	 * bit  2: active high interrupt
+	 * bit  3: active low interrupt
+	 * bit  4: enable debounce
+	 * bit 16: signal is active low
+	 */
+	#interrupt-cells = <2>;
+	#gpio-cells = <2>;
+	gpio-count = <340>;
+	/* 1st cell: gpio interrupt status bit
+	 * 2nd cell: 1st pin
+	 * 3rd cell: # of pins
+	 */
+	gpio-interrupts =
+		<0 0 32>,	/* TL / TQ */
+		<1 32 32>,	/* PIC 1 */
+		<2 32 32>,	/* PIC 1 spare */
+		<7 148 32>,	/* PIC 0 */
+		<8 170 32>,	/* PIC 0 spare */
+		<16 318 22>;	/* FPC */
+
+	gpio-exports {
+		/*
+		 * flags:
+		 * GPIOF_DIR_IN			bit 0=1
+		 * GPIOF_DIR_OUT		bit 0=0
+		 * GPIOF_INIT_HIGH		bit 1=1
+		 *   GPIOF_INIT_HIGH is raw, not translated
+		 * GPIOF_ACTIVE_LOW		bit 2=1
+		 * GPIOF_OPEN_DRAIN		bit 3=1
+		 * GPIOF_OPEN_SOURCE		bit 4=1
+		 * GPIOF_EXPORT			bit 5=1
+		 * GPIOF_EXPORT_CHANGEABLE      bit 6=1
+		 */
+		tl0-rst {
+			pin = < 8 0x24 >;
+		};
+	};
+};
-- 
1.9.1


^ permalink raw reply related

* [PATCH 05/10] gpio: Introduce SAM gpio driver
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Guenter Roeck <groeck@juniper.net>

The SAM GPIO IP block is present in the Juniper PTX series
of routers as part of the SAM FPGA.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/gpio/Kconfig    |  11 +
 drivers/gpio/Makefile   |   1 +
 drivers/gpio/gpio-sam.c | 707 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 719 insertions(+)
 create mode 100644 drivers/gpio/gpio-sam.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 9c91de6..c25dbe9 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -384,6 +384,17 @@ config GPIO_RCAR
 	help
 	  Say yes here to support GPIO on Renesas R-Car SoCs.
 
+config GPIO_SAM
+	tristate "SAM FPGA GPIO"
+	depends on MFD_JUNIPER_SAM
+	default y if MFD_JUNIPER_SAM
+	help
+	  This driver supports the GPIO interfaces on the SAM FPGA which is
+	  present on the relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called gpio-sam.
+
 config GPIO_SPEAR_SPICS
 	bool "ST SPEAr13xx SPI Chip Select as GPIO support"
 	depends on PLAT_SPEAR
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index d397ea5..6691d8c 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_GPIO_RC5T583)	+= gpio-rc5t583.o
 obj-$(CONFIG_GPIO_RDC321X)	+= gpio-rdc321x.o
 obj-$(CONFIG_GPIO_RCAR)		+= gpio-rcar.o
 obj-$(CONFIG_ARCH_SA1100)	+= gpio-sa1100.o
+obj-$(CONFIG_GPIO_SAM)		+= gpio-sam.o
 obj-$(CONFIG_GPIO_SCH)		+= gpio-sch.o
 obj-$(CONFIG_GPIO_SCH311X)	+= gpio-sch311x.o
 obj-$(CONFIG_GPIO_SODAVILLE)	+= gpio-sodaville.o
diff --git a/drivers/gpio/gpio-sam.c b/drivers/gpio/gpio-sam.c
new file mode 100644
index 0000000..5082050
--- /dev/null
+++ b/drivers/gpio/gpio-sam.c
@@ -0,0 +1,707 @@
+/*
+ * Copyright (C) 2012 - 2015 Juniper Networks
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/errno.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mfd/sam.h>
+
+/* gpio status/configuration */
+#define SAM_GPIO_NEG_EDGE	(1 << 8)
+#define SAM_GPIO_NEG_EDGE_EN	(1 << 7)
+#define SAM_GPIO_POS_EDGE	(1 << 6)
+#define SAM_GPIO_POS_EDGE_EN	(1 << 5)
+#define SAM_GPIO_BLINK		(1 << 4)
+#define SAM_GPIO_OUT		(1 << 3)
+#define SAM_GPIO_OUT_TS		(1 << 2)
+#define SAM_GPIO_DEBOUNCE_EN	(1 << 1)
+#define SAM_GPIO_IN		(1 << 0)
+
+#define SAM_GPIO_BASE		0x1000
+
+#define SAM_MAX_NGPIO		512
+
+#define SAM_GPIO_ADDR(addr, nr)	((addr) + SAM_GPIO_BASE + (nr) * sizeof(u32))
+
+struct sam_gpio_irq_group {
+	int start;		/* 1st gpio pin */
+	int count;		/* # of pins in group */
+	int num_enabled;	/* # of enabled interrupts */
+};
+
+/**
+ * struct sam_gpio - GPIO private data structure.
+ * @base:			PCI base address of Memory mapped I/O register.
+ * @dev:			Pointer to device structure.
+ * @gpio:			Data for GPIO infrastructure.
+ * @gpio_base:			1st gpio pin
+ * @gpio_count:			# of gpio pins
+ * @irq_lock:			Lock used by interrupt subsystem
+ * @domain:			Pointer to interrupt domain
+ * @irq:			Interrupt # from parent
+ * @irq_high:			Second interrupt # from parent
+ *				(currently unused)
+ * @irq_group:			Interrupt group descriptions
+ *				(one group per interrupt bit)
+ * @irq_type:			The interrupt type for each gpio pin
+ */
+struct sam_gpio {
+	void __iomem *base;
+	struct device *dev;
+	struct gpio_chip gpio;
+	int gpio_base;
+	int gpio_count;
+	struct mutex irq_lock;
+	struct irq_domain *domain;
+	int irq;
+	int irq_high;
+	struct sam_gpio_irq_group irq_group[18];
+	u8 irq_type[SAM_MAX_NGPIO];
+	struct sam_platform_data *pdata;
+	const char **names;
+	u32 *export_flags;
+};
+#define to_sam(chip)	container_of((chip), struct sam_gpio, gpio)
+
+static void sam_gpio_bitop(struct sam_gpio *sam, unsigned int nr,
+			   u32 bit, bool set)
+{
+	u32 reg;
+
+	reg = ioread32(SAM_GPIO_ADDR(sam->base, nr));
+	if (set)
+		reg |= bit;
+	else
+		reg &= ~bit;
+	iowrite32(reg, SAM_GPIO_ADDR(sam->base, nr));
+	ioread32(SAM_GPIO_ADDR(sam->base, nr));
+}
+
+static int sam_gpio_debounce(struct gpio_chip *chip, unsigned int nr,
+			     unsigned int debounce)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_DEBOUNCE_EN, debounce);
+
+	return 0;
+}
+
+static void sam_gpio_set(struct gpio_chip *chip, unsigned int nr, int val)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, val);
+}
+
+static int sam_gpio_get(struct gpio_chip *chip, unsigned int nr)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	return !!(ioread32(SAM_GPIO_ADDR(sam->base, nr)) & SAM_GPIO_IN);
+}
+
+static int sam_gpio_direction_output(struct gpio_chip *chip, unsigned int nr,
+				     int val)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, val);
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT_TS, false);
+	return 0;
+}
+
+static int sam_gpio_direction_input(struct gpio_chip *chip, unsigned int nr)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT_TS, true);
+	sam_gpio_bitop(sam, nr, SAM_GPIO_OUT, false);
+	return 0;
+}
+
+static void sam_gpio_setup(struct sam_gpio *sam)
+{
+	struct gpio_chip *chip = &sam->gpio;
+
+	chip->parent = sam->dev;
+	chip->label = dev_name(sam->dev);
+	chip->owner = THIS_MODULE;
+	chip->direction_input = sam_gpio_direction_input;
+	chip->get = sam_gpio_get;
+	chip->direction_output = sam_gpio_direction_output;
+	chip->set = sam_gpio_set;
+	chip->set_debounce = sam_gpio_debounce;
+	chip->dbg_show = NULL;
+	chip->base = sam->gpio_base;
+	chip->ngpio = sam->gpio_count;
+#ifdef CONFIG_OF_GPIO
+	chip->of_node = sam->dev->of_node;
+#endif
+	chip->names = sam->names;
+}
+
+static int sam_of_get_exports(struct device *dev, struct sam_gpio *sam)
+{
+	struct device_node *child, *exports;
+	int err = 0;
+
+	if (dev->of_node == NULL)
+		return 0;	/* No FDT node, we are done */
+
+	exports = of_get_child_by_name(dev->of_node, "gpio-exports");
+	if (exports == NULL)
+		return 0;	/* No exports, we are done */
+
+	if (of_get_child_count(exports) == 0)
+		return 0;	/* No children, we are done */
+
+	sam->names = devm_kzalloc(dev, sizeof(char *) * sam->gpio_count,
+				  GFP_KERNEL);
+	if (sam->names == NULL) {
+		err = -ENOMEM;
+		goto error;
+	}
+	sam->export_flags =
+		devm_kzalloc(dev, sizeof(u32) * sam->gpio_count, GFP_KERNEL);
+	if (sam->export_flags == NULL) {
+		err = -ENOMEM;
+		goto error;
+	}
+	for_each_child_of_node(exports, child) {
+		const char *label;
+		u32 pin, flags;
+
+		label = of_get_property(child, "label", NULL) ? : child->name;
+		err = of_property_read_u32_index(child, "pin", 0, &pin);
+		if (err)
+			break;
+		if (pin >= sam->gpio_count) {
+			err = -EINVAL;
+			break;
+		}
+		err = of_property_read_u32_index(child, "pin", 1, &flags);
+		if (err)
+			break;
+		/*
+		 * flags:
+		 * GPIOF_DIR_IN			bit 0=1
+		 * GPIOF_DIR_OUT		bit 0=0
+		 *	GPIOF_INIT_HIGH		bit 1=1
+		 * GPIOF_ACTIVE_LOW		bit 2=1
+		 * GPIOF_OPEN_DRAIN		bit 3=1
+		 * GPIOF_OPEN_SOURCE		bit 4=1
+		 * GPIOF_EXPORT			bit 5=1
+		 * GPIOF_EXPORT_CHANGEABLE	bit 6=1
+		 */
+		sam->names[pin] = label;
+		sam->export_flags[pin] = flags;
+	}
+error:
+	of_node_put(exports);
+	return err;
+}
+
+static int sam_gpio_of_init(struct device *dev, struct sam_gpio *sam)
+{
+	int err;
+	u32 val;
+	const u32 *igroup;
+	u32 group, start, count;
+	int i, iglen, ngpio;
+
+	if (of_have_populated_dt() && !dev->of_node) {
+		dev_err(dev, "No device node\n");
+		return -ENODEV;
+	}
+
+	err = of_property_read_u32(dev->of_node, "gpio-base", &val);
+	if (err)
+		val = -1;
+	sam->gpio_base = val;
+
+	err = of_property_read_u32(dev->of_node, "gpio-count", &val);
+	if (!err) {
+		if (val > SAM_MAX_NGPIO)
+			val = SAM_MAX_NGPIO;
+		sam->gpio_count = val;
+	}
+	/* validate gpio_count against chip data. Abort if chip data is bad. */
+	ngpio = ioread32(sam->base + 2 * sizeof(u32)) & 0xffff;
+	if (!ngpio || ngpio > SAM_MAX_NGPIO)
+		return -ENODEV;
+
+	if (!sam->gpio_count || sam->gpio_count > ngpio)
+		sam->gpio_count = ngpio;
+
+	igroup = of_get_property(dev->of_node, "gpio-interrupts", &iglen);
+	if (igroup) {
+		iglen /= sizeof(u32);
+		if (iglen < 3 || iglen % 3)
+			return -EINVAL;
+		iglen /= 3;
+		for (i = 0; i < iglen; i++) {
+			group = be32_to_cpu(igroup[i * 3]);
+			if (group >= ARRAY_SIZE(sam->irq_group))
+				return -EINVAL;
+			start = be32_to_cpu(igroup[i * 3 + 1]);
+			count = be32_to_cpu(igroup[i * 3 + 2]);
+			if (start >= sam->gpio_count || count == 0 ||
+			    start + count > sam->gpio_count)
+				return -EINVAL;
+			sam->irq_group[group].start = start;
+			sam->irq_group[group].count = count;
+		}
+	}
+
+	err = sam_of_get_exports(dev, sam);
+	return err;
+}
+
+static int sam_gpio_pin_to_irq_bit(struct sam_gpio *sam, int pin)
+{
+	int bit;
+
+	for (bit = 0; bit < ARRAY_SIZE(sam->irq_group); bit++) {
+		struct sam_gpio_irq_group *irq_group = &sam->irq_group[bit];
+
+		if (irq_group->count &&
+		    pin >= irq_group->start &&
+		    pin <= irq_group->start + irq_group->count)
+			return bit;
+	}
+	return -EINVAL;
+}
+
+static bool sam_gpio_irq_handle_group(struct sam_gpio *sam,
+				      struct sam_gpio_irq_group *irq_group)
+{
+	unsigned int virq = 0;
+	bool handled = false;
+	bool repeat;
+	int i;
+
+	/* no irq_group for the interrupt bit */
+	if (!irq_group->count)
+		return false;
+
+	WARN_ON(irq_group->num_enabled == 0);
+	do {
+		repeat = false;
+		for (i = 0; i < irq_group->count; i++) {
+			int pin = irq_group->start + i;
+			bool low, high;
+			u32 regval;
+			u8 type;
+
+			regval = ioread32(SAM_GPIO_ADDR(sam->base, pin));
+			/*
+			 * write back status to clear POS_EDGE and NEG_EDGE
+			 * status for this GPIO pin (status bits are
+			 * clear-on-one). This is necessary to clear the
+			 * high level interrupt status.
+			 * Also consider the interrupt to be handled in that
+			 * case, even if there is no taker.
+			 */
+			if (regval & (SAM_GPIO_POS_EDGE | SAM_GPIO_NEG_EDGE)) {
+				iowrite32(regval,
+					  SAM_GPIO_ADDR(sam->base, pin));
+				ioread32(SAM_GPIO_ADDR(sam->base, pin));
+				handled = true;
+			}
+
+			/*
+			 * Check if the pin changed its state.
+			 * If it did, and if the expected condition applies,
+			 * generate a virtual interrupt.
+			 * A pin can only generate an interrupt if
+			 * - interrupts are enabled for it
+			 * - it is configured as input
+			 */
+
+			if (!sam->irq_type[pin])
+				continue;
+			if (!(regval & SAM_GPIO_OUT_TS))
+				continue;
+
+			high = regval & (SAM_GPIO_IN | SAM_GPIO_POS_EDGE);
+			low = !(regval & SAM_GPIO_IN) ||
+				(regval & SAM_GPIO_NEG_EDGE);
+			type = sam->irq_type[pin];
+			if (((type & IRQ_TYPE_EDGE_RISING) &&
+			     (regval & SAM_GPIO_POS_EDGE)) ||
+			    ((type & IRQ_TYPE_EDGE_FALLING) &&
+			     (regval & SAM_GPIO_NEG_EDGE)) ||
+			    ((type & IRQ_TYPE_LEVEL_LOW) && low) ||
+			    ((type & IRQ_TYPE_LEVEL_HIGH) && high)) {
+				virq = irq_find_mapping(sam->domain, pin);
+				handle_nested_irq(virq);
+				if (type & (IRQ_TYPE_LEVEL_LOW
+					    | IRQ_TYPE_LEVEL_HIGH))
+					repeat = true;
+			}
+		}
+		schedule();
+	} while (repeat);
+
+	return handled;
+}
+
+static irqreturn_t sam_gpio_irq_handler(int irq, void *data)
+{
+	struct sam_gpio *sam = data;
+	struct sam_platform_data *pdata = sam->pdata;
+	irqreturn_t ret = IRQ_NONE;
+	bool handled;
+	u32 status;
+
+	do {
+		handled = false;
+		status = pdata->irq_status(sam->dev->parent, SAM_IRQ_GPIO,
+					   sam->irq);
+		pdata->irq_status_clear(sam->dev->parent, SAM_IRQ_GPIO,
+					sam->irq, status);
+		while (status) {
+			unsigned int bit;
+
+			bit = __ffs(status);
+			status &= ~(1 << bit);
+			handled =
+			  sam_gpio_irq_handle_group(sam, &sam->irq_group[bit]);
+			if (handled)
+				ret = IRQ_HANDLED;
+		}
+	} while (handled);
+
+	return ret;
+}
+
+static int sam_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+	struct sam_gpio *sam = to_sam(chip);
+
+	return irq_create_mapping(sam->domain, offset);
+}
+
+static void sam_irq_mask(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+	struct sam_platform_data *pdata = sam->pdata;
+	int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+	if (bit < 0)
+		return;
+
+	if (--sam->irq_group[bit].num_enabled <= 0) {
+		pdata->disable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq,
+				   1 << bit);
+	}
+}
+
+static void sam_irq_unmask(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+	struct sam_platform_data *pdata = sam->pdata;
+	int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+	if (bit < 0)
+		return;
+
+	sam->irq_group[bit].num_enabled++;
+	pdata->enable_irq(sam->dev->parent, SAM_IRQ_GPIO, sam->irq, 1 << bit);
+}
+
+static int sam_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+	int bit = sam_gpio_pin_to_irq_bit(sam, data->hwirq);
+
+	if (bit < 0)
+		return bit;
+
+	sam->irq_type[data->hwirq] = type & 0x0f;
+	sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_OUT_TS, true);
+	sam_gpio_bitop(sam, data->hwirq, SAM_GPIO_DEBOUNCE_EN, type & 0x10);
+	sam_gpio_bitop(sam, data->hwirq,
+		       SAM_GPIO_POS_EDGE_EN | SAM_GPIO_POS_EDGE,
+		       type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH));
+	sam_gpio_bitop(sam, data->hwirq,
+		       SAM_GPIO_NEG_EDGE_EN | SAM_GPIO_NEG_EDGE,
+		       type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW));
+
+	return 0;
+}
+
+static void sam_irq_bus_lock(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&sam->irq_lock);
+}
+
+static void sam_irq_bus_unlock(struct irq_data *data)
+{
+	struct sam_gpio *sam = irq_data_get_irq_chip_data(data);
+
+	/* Synchronize interrupts to chip */
+
+	mutex_unlock(&sam->irq_lock);
+}
+
+static struct irq_chip sam_irq_chip = {
+	.name = "gpio-sam",
+	.irq_mask = sam_irq_mask,
+	.irq_unmask = sam_irq_unmask,
+	.irq_set_type = sam_irq_set_type,
+	.irq_bus_lock = sam_irq_bus_lock,
+	.irq_bus_sync_unlock = sam_irq_bus_unlock,
+};
+
+static int sam_gpio_irq_map(struct irq_domain *domain, unsigned int irq,
+			    irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(irq, domain->host_data);
+	irq_set_chip(irq, &sam_irq_chip);
+	irq_set_nested_thread(irq, true);
+
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops sam_gpio_irq_domain_ops = {
+	.map = sam_gpio_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static int sam_gpio_irq_setup(struct device *dev, struct sam_gpio *sam)
+{
+	int ret;
+
+	sam->domain = irq_domain_add_linear(dev->of_node,
+					    sam->gpio_count,
+					    &sam_gpio_irq_domain_ops,
+					    sam);
+	if (sam->domain == NULL)
+		return -ENOMEM;
+
+	ret = devm_request_threaded_irq(dev, sam->irq, NULL,
+					sam_gpio_irq_handler,
+					IRQF_ONESHOT,
+					dev_name(dev), sam);
+	if (ret)
+		goto out_remove_domain;
+
+	sam->gpio.to_irq = sam_gpio_to_irq;
+
+	if (!try_module_get(dev->parent->driver->owner)) {
+		ret = -EINVAL;
+		goto out_remove_domain;
+	}
+
+	return 0;
+
+out_remove_domain:
+	irq_domain_remove(sam->domain);
+	sam->domain = NULL;
+	return ret;
+}
+
+static void sam_gpio_irq_teardown(struct device *dev, struct sam_gpio *sam)
+{
+	int i, irq;
+	struct sam_platform_data *pdata = sam->pdata;
+
+	pdata->disable_irq(dev->parent, SAM_IRQ_GPIO, sam->irq, 0xffffffff);
+
+	for (i = 0; i < sam->gpio_count; i++) {
+		irq = irq_find_mapping(sam->domain, i);
+		if (irq > 0)
+			irq_dispose_mapping(irq);
+	}
+	irq_domain_remove(sam->domain);
+	module_put(dev->parent->driver->owner);
+}
+
+static int sam_gpio_unexport(struct sam_gpio *sam)
+{
+	int i;
+
+	if (!sam->export_flags)
+		return 0;
+
+	/* un-export all auto-exported pins */
+	for (i = 0; i < sam->gpio_count; i++) {
+		struct gpio_desc *desc = gpio_to_desc(sam->gpio.base + i);
+
+		if (desc == NULL)
+			continue;
+
+		if (sam->export_flags[i] & GPIOF_EXPORT)
+			gpiochip_free_own_desc(desc);
+	}
+	return 0;
+}
+
+static int sam_gpio_export(struct sam_gpio *sam)
+{
+	int i, ret;
+
+	if (!sam->export_flags)
+		return 0;
+
+	/* auto-export pins as requested */
+
+	for (i = 0; i < sam->gpio_count; i++) {
+		u32 flags = sam->export_flags[i];
+		struct gpio_desc *desc;
+
+		/* request and initialize exported pins */
+		if (!(flags & GPIOF_EXPORT))
+			continue;
+
+		desc  = gpiochip_request_own_desc(&sam->gpio, i, "sam-export");
+		if (IS_ERR(desc)) {
+			ret = PTR_ERR(desc);
+			goto error;
+		}
+		if (flags & GPIOF_DIR_IN) {
+			ret = gpiod_direction_input(desc);
+			if (ret)
+				goto error;
+		} else {
+			ret = gpiod_direction_output(desc, flags &
+						    (GPIOF_OUT_INIT_HIGH |
+						     GPIOF_ACTIVE_LOW));
+			if (ret)
+				goto error;
+		}
+		ret = gpiod_export(desc, flags & GPIOF_EXPORT_CHANGEABLE);
+
+		if (ret)
+			goto error;
+	}
+	return 0;
+
+error:
+	sam_gpio_unexport(sam);
+	return ret;
+}
+
+static int sam_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct sam_gpio *sam;
+	struct resource *res;
+	int ret;
+	struct sam_platform_data *pdata = dev_get_platdata(&pdev->dev);
+
+	sam = devm_kzalloc(dev, sizeof(*sam), GFP_KERNEL);
+	if (sam == NULL)
+		return -ENOMEM;
+
+	sam->dev = dev;
+	sam->pdata = pdata;
+	platform_set_drvdata(pdev, sam);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	sam->irq = platform_get_irq(pdev, 0);
+	sam->irq_high = platform_get_irq(pdev, 1);
+
+	sam->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (!sam->base)
+		return -ENOMEM;
+
+	mutex_init(&sam->irq_lock);
+
+	ret = sam_gpio_of_init(dev, sam);
+	if (ret)
+		return ret;
+
+	sam_gpio_setup(sam);
+
+	if (pdata && sam->irq >= 0 && of_find_property(dev->of_node,
+					      "interrupt-controller", NULL)) {
+		ret = sam_gpio_irq_setup(dev, sam);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = gpiochip_add(&sam->gpio);
+	if (ret)
+		goto teardown;
+
+	ret = sam_gpio_export(sam);
+	if (ret)
+		goto teardown_remove;
+
+	return 0;
+
+teardown_remove:
+	gpiochip_remove(&sam->gpio);
+
+teardown:
+	if (sam->domain)
+		sam_gpio_irq_teardown(dev, sam);
+	return ret;
+}
+
+static int sam_gpio_remove(struct platform_device *pdev)
+{
+	struct sam_gpio *sam = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+
+	dev_info(dev, "remove\n");
+
+	sam_gpio_unexport(sam);
+
+	if (sam->domain)
+		sam_gpio_irq_teardown(dev, sam);
+
+	gpiochip_remove(&sam->gpio);
+
+	return 0;
+}
+
+static const struct of_device_id sam_gpio_ids[] = {
+	{ .compatible = "jnx,gpio-sam", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sam_gpio_ids);
+
+static struct platform_driver sam_gpio_driver = {
+	.driver = {
+		.name = "gpio-sam",
+		.owner  = THIS_MODULE,
+		.of_match_table = sam_gpio_ids,
+	},
+	.probe = sam_gpio_probe,
+	.remove = sam_gpio_remove,
+};
+
+module_platform_driver(sam_gpio_driver);
+
+MODULE_DESCRIPTION("SAM FPGA GPIO Driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1


^ permalink raw reply related

* [PATCH 04/10] i2c: i2c-sam: Add device tree bindings
From: Pantelis Antoniou @ 2016-10-07 15:18 UTC (permalink / raw)
  To: Lee Jones
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Frank Rowand, Wolfram Sang, David Woodhouse, Brian Norris,
	Florian Fainelli, Wim Van Sebroeck, Peter Rosin, Debjit Ghosh,
	Georgi Vlaev, Guenter Roeck, Maryam Seraj, Pantelis Antoniou,
	devicetree, linux-kernel, linux-gpio, linux-i2c, linux-mtd, li
In-Reply-To: <1475853518-22264-1-git-send-email-pantelis.antoniou@konsulko.com>

From: Georgi Vlaev <gvlaev@juniper.net>

Add binding document for the i2c driver of SAM FPGA.

Signed-off-by: Georgi Vlaev <gvlaev@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 .../devicetree/bindings/i2c/i2c-sam-mux.txt        | 20 ++++++++++
 Documentation/devicetree/bindings/i2c/i2c-sam.txt  | 44 ++++++++++++++++++++++
 2 files changed, 64 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sam.txt

diff --git a/Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt b/Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
new file mode 100644
index 0000000..10ddffa
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-sam-mux.txt
@@ -0,0 +1,20 @@
+Juniper's SAM FPGA I2C accelerator mux
+
+The SAM FPGA I2C mux is present only on Juniper SAM FPGA PTX series
+of routers.
+
+The definition of the i2c sam bus is located in the i2c-sam.txt document.
+
+Required properties:
+- compatible: should be "jnx,i2c-sam-mux".
+- reg: master number and mux number.
+
+Optional properties:
+- speed: If present must be either 100000 or 400000. No other values supported.
+
+Examples:
+
+pe1i2c: i2c-sam-mux@1,0 {
+	compatible = "jnx,i2c-sam-mux";
+	reg = <1 0>;
+};
diff --git a/Documentation/devicetree/bindings/i2c/i2c-sam.txt b/Documentation/devicetree/bindings/i2c/i2c-sam.txt
new file mode 100644
index 0000000..4830b48
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-sam.txt
@@ -0,0 +1,44 @@
+Juniper's SAM FPGA I2C accelerator
+
+The SAM FPGA accelerator is used to connect the large number of
+I2C muxes that are present on Juniper PTX series of routers.
+While it's an i2c bus, no other devices are located besides
+i2c-sam-mux devices.
+
+The definition of the i2c sam mux is located in the i2c-sam-mux.txt document.
+
+Required properties:
+- compatible: should be "jnx,i2c-sam".
+- #address-cells: should be 2.
+- #size-cells: should be 0.
+- mux-channels: number of mux channels present
+
+Optional properties:
+- reg: offset and length of the register set for the device are optional since
+  typically the register range is provided by the parent SAM MFD device.
+- master-offset: Offset of where the master register memory starts.
+  Default value is 0x8000.
+- reverse-fill: Fill the start entries of transactions in reverse order
+- priority-tables: Use the pre-programmed priority tables in the FPGA
+- i2c-options: list of options to be written to the option field in the
+  FPGA controlling things like SCL push-pull drives, hold-times, etc.
+- bus-range: start of bus master range and number of masters.
+
+Examples:
+
+i2c-sam {
+	compatible = "jnx,i2c-sam";
+	mux-channels = <2>;
+	#size-cells = <0>;
+	#address-cells = <2>;
+
+	/* PE0 */ pe0i2c: i2c-sam-mux@0,0 {
+		compatible = "jnx,i2c-sam-mux";
+		reg = <0 0>;
+	};
+
+	/* PE1 */ pe1i2c: i2c-sam-mux@1,0 {
+		compatible = "jnx,i2c-sam-mux";
+		reg = <1 0>;
+	};
+};
-- 
1.9.1

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox