* [PATCH net-next v3 08/10] net: dsa: Add support for platform data
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Allow drivers to use the new DSA API with platform data. Most of the
code in net/dsa/dsa2.c does not rely so much on device_nodes and can get
the same information from platform_data instead.
We purposely do not support distributed configurations with platform
data, so drivers should be providing a pointer to a 'struct
dsa_chip_data' structure if they wish to communicate per-port layout.
Multiple CPUs port could potentially be supported and dsa_chip_data is
extended to receive up to one reference to an upstream network device
per port described by a dsa_chip_data structure.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
include/net/dsa.h | 6 ++++
net/dsa/dsa2.c | 101 ++++++++++++++++++++++++++++++++++++++++++++----------
2 files changed, 89 insertions(+), 18 deletions(-)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 16a502a6c26a..491008792e4d 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -42,6 +42,11 @@ struct dsa_chip_data {
struct device *host_dev;
int sw_addr;
+ /*
+ * Reference to network devices
+ */
+ struct device *netdev[DSA_MAX_PORTS];
+
/* set to size of eeprom if supported by the switch */
int eeprom_len;
@@ -140,6 +145,7 @@ struct dsa_switch_tree {
};
struct dsa_port {
+ const char *name;
struct net_device *netdev;
struct device_node *dn;
unsigned int ageing_time;
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index cd91070b5467..598229b02fd3 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -79,19 +79,28 @@ static void dsa_dst_del_ds(struct dsa_switch_tree *dst,
kref_put(&dst->refcount, dsa_free_dst);
}
+/* For platform data configurations, we need to have a valid name argument to
+ * differentiate a disabled port from an enabled one
+ */
static bool dsa_port_is_valid(struct dsa_port *port)
{
- return !!port->dn;
+ return !!(port->dn || port->name);
}
static bool dsa_port_is_dsa(struct dsa_port *port)
{
- return !!of_parse_phandle(port->dn, "link", 0);
+ if (port->name && !strcmp(port->name, "dsa"))
+ return true;
+ else
+ return !!of_parse_phandle(port->dn, "link", 0);
}
static bool dsa_port_is_cpu(struct dsa_port *port)
{
- return !!of_parse_phandle(port->dn, "ethernet", 0);
+ if (port->name && !strcmp(port->name, "cpu"))
+ return true;
+ else
+ return !!of_parse_phandle(port->dn, "ethernet", 0);
}
static bool dsa_ds_find_port_dn(struct dsa_switch *ds,
@@ -251,10 +260,11 @@ static void dsa_cpu_port_unapply(struct dsa_port *port, u32 index,
static int dsa_user_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
- const char *name;
+ const char *name = port->name;
int err;
- name = of_get_property(port->dn, "label", NULL);
+ if (port->dn)
+ name = of_get_property(port->dn, "label", NULL);
if (!name)
name = "eth%d";
@@ -439,11 +449,15 @@ static int dsa_cpu_parse(struct dsa_port *port, u32 index,
struct net_device *ethernet_dev;
struct device_node *ethernet;
- ethernet = of_parse_phandle(port->dn, "ethernet", 0);
- if (!ethernet)
- return -EINVAL;
+ if (port->dn) {
+ ethernet = of_parse_phandle(port->dn, "ethernet", 0);
+ if (!ethernet)
+ return -EINVAL;
+ ethernet_dev = of_find_net_device_by_node(ethernet);
+ } else {
+ ethernet_dev = dev_to_net_device(ds->cd->netdev[index]);
+ }
- ethernet_dev = of_find_net_device_by_node(ethernet);
if (!ethernet_dev)
return -EPROBE_DEFER;
@@ -546,6 +560,33 @@ static int dsa_parse_ports_dn(struct device_node *ports, struct dsa_switch *ds)
return 0;
}
+static int dsa_parse_ports(struct dsa_chip_data *cd, struct dsa_switch *ds)
+{
+ bool valid_name_found = false;
+ unsigned int i;
+
+ for (i = 0; i < DSA_MAX_PORTS; i++) {
+ if (!cd->port_names[i])
+ continue;
+
+ ds->ports[i].name = cd->port_names[i];
+
+ /* Initialize enabled_port_mask now for drv->setup()
+ * to have access to a correct value, just like what
+ * net/dsa/dsa.c::dsa_switch_setup_one does.
+ */
+ if (!dsa_port_is_cpu(&ds->ports[i]))
+ ds->enabled_port_mask |= 1 << i;
+
+ valid_name_found = true;
+ }
+
+ if (!valid_name_found && i == DSA_MAX_PORTS)
+ return -EINVAL;
+
+ return 0;
+}
+
static int dsa_parse_member_dn(struct device_node *np, u32 *tree, u32 *index)
{
int err;
@@ -570,6 +611,18 @@ static int dsa_parse_member_dn(struct device_node *np, u32 *tree, u32 *index)
return 0;
}
+static int dsa_parse_member(struct dsa_chip_data *pd, u32 *tree, u32 *index)
+{
+ if (!pd)
+ return -ENODEV;
+
+ /* We do not support complex trees with dsa_chip_data */
+ *tree = 0;
+ *index = 0;
+
+ return 0;
+}
+
static struct device_node *dsa_get_ports(struct dsa_switch *ds,
struct device_node *np)
{
@@ -586,23 +639,34 @@ static struct device_node *dsa_get_ports(struct dsa_switch *ds,
static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
+ struct dsa_chip_data *pdata = dev->platform_data;
struct device_node *np = dev->of_node;
struct dsa_switch_tree *dst;
struct device_node *ports;
u32 tree, index;
int i, err;
- err = dsa_parse_member_dn(np, &tree, &index);
- if (err)
- return err;
+ if (np) {
+ err = dsa_parse_member_dn(np, &tree, &index);
+ if (err)
+ return err;
- ports = dsa_get_ports(ds, np);
- if (IS_ERR(ports))
- return PTR_ERR(ports);
+ ports = dsa_get_ports(ds, np);
+ if (IS_ERR(ports))
+ return PTR_ERR(ports);
- err = dsa_parse_ports_dn(ports, ds);
- if (err)
- return err;
+ err = dsa_parse_ports_dn(ports, ds);
+ if (err)
+ return err;
+ } else {
+ err = dsa_parse_member(pdata, &tree, &index);
+ if (err)
+ return err;
+
+ err = dsa_parse_ports(pdata, ds);
+ if (err)
+ return err;
+ }
dst = dsa_get_dst(tree);
if (!dst) {
@@ -618,6 +682,7 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
ds->dst = dst;
ds->index = index;
+ ds->cd = pdata;
/* Initialize the routing table */
for (i = 0; i < DSA_MAX_SWITCHES; ++i)
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 07/10] net: Relocate dev_to_net_device() into core
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
dev_to_net_device() is moved from net/dsa/dsa.c to net/core/dev.c since
it going to be used by net/dsa/dsa2.c and the namespace of the function
justifies making it available to other users potentially.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
include/linux/netdevice.h | 2 ++
net/core/dev.c | 19 +++++++++++++++++++
net/dsa/dsa.c | 18 ------------------
3 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 97ae0ac513ee..6d021c37b774 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -4390,4 +4390,6 @@ do { \
#define PTYPE_HASH_SIZE (16)
#define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1)
+struct net_device *dev_to_net_device(struct device *dev);
+
#endif /* _LINUX_NETDEVICE_H */
diff --git a/net/core/dev.c b/net/core/dev.c
index ad5959e56116..7547e2ccc06b 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -8128,6 +8128,25 @@ const char *netdev_drivername(const struct net_device *dev)
return empty;
}
+struct net_device *dev_to_net_device(struct device *dev)
+{
+ struct device *d;
+
+ d = device_find_class(dev, "net");
+ if (d) {
+ struct net_device *nd;
+
+ nd = to_net_dev(d);
+ dev_hold(nd);
+ put_device(d);
+
+ return nd;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(dev_to_net_device);
+
static void __netdev_printk(const char *level, const struct net_device *dev,
struct va_format *vaf)
{
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 77fa4c4f5828..6c264f92fec5 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -473,24 +473,6 @@ struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev)
}
EXPORT_SYMBOL_GPL(dsa_host_dev_to_mii_bus);
-static struct net_device *dev_to_net_device(struct device *dev)
-{
- struct device *d;
-
- d = device_find_class(dev, "net");
- if (d != NULL) {
- struct net_device *nd;
-
- nd = to_net_dev(d);
- dev_hold(nd);
- put_device(d);
-
- return nd;
- }
-
- return NULL;
-}
-
#ifdef CONFIG_OF
static int dsa_of_setup_routing_table(struct dsa_platform_data *pd,
struct dsa_chip_data *cd,
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 06/10] net: dsa: Migrate to device_find_class()
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Now that the base device driver code provides an identical
implementation of dev_find_class() utilize device_find_class() instead
of our own version of it.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa.c | 22 ++--------------------
1 file changed, 2 insertions(+), 20 deletions(-)
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 2306d1b87c83..77fa4c4f5828 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -455,29 +455,11 @@ EXPORT_SYMBOL_GPL(dsa_switch_resume);
#endif
/* platform driver init and cleanup *****************************************/
-static int dev_is_class(struct device *dev, void *class)
-{
- if (dev->class != NULL && !strcmp(dev->class->name, class))
- return 1;
-
- return 0;
-}
-
-static struct device *dev_find_class(struct device *parent, char *class)
-{
- if (dev_is_class(parent, class)) {
- get_device(parent);
- return parent;
- }
-
- return device_find_child(parent, class, dev_is_class);
-}
-
struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev)
{
struct device *d;
- d = dev_find_class(dev, "mdio_bus");
+ d = device_find_class(dev, "mdio_bus");
if (d != NULL) {
struct mii_bus *bus;
@@ -495,7 +477,7 @@ static struct net_device *dev_to_net_device(struct device *dev)
{
struct device *d;
- d = dev_find_class(dev, "net");
+ d = device_find_class(dev, "net");
if (d != NULL) {
struct net_device *nd;
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 05/10] drivers: base: Add device_find_class()
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Add a helper function to lookup a device reference given a class name.
This is a preliminary patch to remove adhoc code from net/dsa/dsa.c and
make it more generic.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/base/core.c | 19 +++++++++++++++++++
include/linux/device.h | 1 +
2 files changed, 20 insertions(+)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 020ea7f05520..3dd6047c10d8 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2065,6 +2065,25 @@ struct device *device_find_child(struct device *parent, void *data,
}
EXPORT_SYMBOL_GPL(device_find_child);
+static int dev_is_class(struct device *dev, void *class)
+{
+ if (dev->class != NULL && !strcmp(dev->class->name, class))
+ return 1;
+
+ return 0;
+}
+
+struct device *device_find_class(struct device *parent, char *class)
+{
+ if (dev_is_class(parent, class)) {
+ get_device(parent);
+ return parent;
+ }
+
+ return device_find_child(parent, class, dev_is_class);
+}
+EXPORT_SYMBOL_GPL(device_find_class);
+
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
diff --git a/include/linux/device.h b/include/linux/device.h
index 491b4c0ca633..8d37f5ecb972 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -1120,6 +1120,7 @@ extern int device_for_each_child_reverse(struct device *dev, void *data,
int (*fn)(struct device *dev, void *data));
extern struct device *device_find_child(struct device *dev, void *data,
int (*match)(struct device *dev, void *data));
+extern struct device *device_find_class(struct device *parent, char *class);
extern int device_rename(struct device *dev, const char *new_name);
extern int device_move(struct device *dev, struct device *new_parent,
enum dpm_order dpm_order);
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 04/10] net: dsa: Move ports assignment closer to error checking
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Move the assignment of ports in _dsa_register_switch() closer to where
it is checked, no functional change. Re-order declarations to be
preserve the inverted christmas tree style.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa2.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 04ab62251fe3..cd91070b5467 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -587,8 +587,8 @@ static struct device_node *dsa_get_ports(struct dsa_switch *ds,
static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
struct device_node *np = dev->of_node;
- struct device_node *ports = dsa_get_ports(ds, np);
struct dsa_switch_tree *dst;
+ struct device_node *ports;
u32 tree, index;
int i, err;
@@ -596,6 +596,7 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
if (err)
return err;
+ ports = dsa_get_ports(ds, np);
if (IS_ERR(ports))
return PTR_ERR(ports);
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 03/10] net: dsa: Suffix function manipulating device_node with _dn
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Make it clear that these functions take a device_node structure pointer
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa2.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 6e3675220fef..04ab62251fe3 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -94,8 +94,8 @@ static bool dsa_port_is_cpu(struct dsa_port *port)
return !!of_parse_phandle(port->dn, "ethernet", 0);
}
-static bool dsa_ds_find_port(struct dsa_switch *ds,
- struct device_node *port)
+static bool dsa_ds_find_port_dn(struct dsa_switch *ds,
+ struct device_node *port)
{
u32 index;
@@ -105,8 +105,8 @@ static bool dsa_ds_find_port(struct dsa_switch *ds,
return false;
}
-static struct dsa_switch *dsa_dst_find_port(struct dsa_switch_tree *dst,
- struct device_node *port)
+static struct dsa_switch *dsa_dst_find_port_dn(struct dsa_switch_tree *dst,
+ struct device_node *port)
{
struct dsa_switch *ds;
u32 index;
@@ -116,7 +116,7 @@ static struct dsa_switch *dsa_dst_find_port(struct dsa_switch_tree *dst,
if (!ds)
continue;
- if (dsa_ds_find_port(ds, port))
+ if (dsa_ds_find_port_dn(ds, port))
return ds;
}
@@ -137,7 +137,7 @@ static int dsa_port_complete(struct dsa_switch_tree *dst,
if (!link)
break;
- dst_ds = dsa_dst_find_port(dst, link);
+ dst_ds = dsa_dst_find_port_dn(dst, link);
of_node_put(link);
if (!dst_ds)
@@ -546,7 +546,7 @@ static int dsa_parse_ports_dn(struct device_node *ports, struct dsa_switch *ds)
return 0;
}
-static int dsa_parse_member(struct device_node *np, u32 *tree, u32 *index)
+static int dsa_parse_member_dn(struct device_node *np, u32 *tree, u32 *index)
{
int err;
@@ -592,7 +592,7 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
u32 tree, index;
int i, err;
- err = dsa_parse_member(np, &tree, &index);
+ err = dsa_parse_member_dn(np, &tree, &index);
if (err)
return err;
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 02/10] net: dsa: Make most functions take a dsa_port argument
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
In preparation for allowing platform data, and therefore no valid
device_node pointer, make most DSA functions takes a pointer to a
dsa_port structure whenever possible. While at it, introduce a
dsa_port_is_valid() helper function which checks whether port->dn is
NULL or not at the moment.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa.c | 15 ++++++++------
net/dsa/dsa2.c | 61 +++++++++++++++++++++++++++++-------------------------
net/dsa/dsa_priv.h | 4 ++--
3 files changed, 44 insertions(+), 36 deletions(-)
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index fd532487dfdf..2306d1b87c83 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -110,8 +110,9 @@ dsa_switch_probe(struct device *parent, struct device *host_dev, int sw_addr,
/* basic switch operations **************************************************/
int dsa_cpu_dsa_setup(struct dsa_switch *ds, struct device *dev,
- struct device_node *port_dn, int port)
+ struct dsa_port *dport, int port)
{
+ struct device_node *port_dn = dport->dn;
struct phy_device *phydev;
int ret, mode;
@@ -141,15 +142,15 @@ int dsa_cpu_dsa_setup(struct dsa_switch *ds, struct device *dev,
static int dsa_cpu_dsa_setups(struct dsa_switch *ds, struct device *dev)
{
- struct device_node *port_dn;
+ struct dsa_port *dport;
int ret, port;
for (port = 0; port < DSA_MAX_PORTS; port++) {
if (!(dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)))
continue;
- port_dn = ds->ports[port].dn;
- ret = dsa_cpu_dsa_setup(ds, dev, port_dn, port);
+ dport = &ds->ports[port];
+ ret = dsa_cpu_dsa_setup(ds, dev, dport, port);
if (ret)
return ret;
}
@@ -366,8 +367,10 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
return ds;
}
-void dsa_cpu_dsa_destroy(struct device_node *port_dn)
+void dsa_cpu_dsa_destroy(struct dsa_port *port)
{
+ struct device_node *port_dn = port->dn;
+
if (of_phy_is_fixed_link(port_dn))
of_phy_deregister_fixed_link(port_dn);
}
@@ -393,7 +396,7 @@ static void dsa_switch_destroy(struct dsa_switch *ds)
for (port = 0; port < DSA_MAX_PORTS; port++) {
if (!(dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)))
continue;
- dsa_cpu_dsa_destroy(ds->ports[port].dn);
+ dsa_cpu_dsa_destroy(&ds->ports[port]);
/* Clearing a bit which is not set does no harm */
ds->cpu_port_mask |= ~(1 << port);
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 4170f7ea8e28..6e3675220fef 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -79,14 +79,19 @@ static void dsa_dst_del_ds(struct dsa_switch_tree *dst,
kref_put(&dst->refcount, dsa_free_dst);
}
-static bool dsa_port_is_dsa(struct device_node *port)
+static bool dsa_port_is_valid(struct dsa_port *port)
{
- return !!of_parse_phandle(port, "link", 0);
+ return !!port->dn;
}
-static bool dsa_port_is_cpu(struct device_node *port)
+static bool dsa_port_is_dsa(struct dsa_port *port)
{
- return !!of_parse_phandle(port, "ethernet", 0);
+ return !!of_parse_phandle(port->dn, "link", 0);
+}
+
+static bool dsa_port_is_cpu(struct dsa_port *port)
+{
+ return !!of_parse_phandle(port->dn, "ethernet", 0);
}
static bool dsa_ds_find_port(struct dsa_switch *ds,
@@ -120,7 +125,7 @@ static struct dsa_switch *dsa_dst_find_port(struct dsa_switch_tree *dst,
static int dsa_port_complete(struct dsa_switch_tree *dst,
struct dsa_switch *src_ds,
- struct device_node *port,
+ struct dsa_port *port,
u32 src_port)
{
struct device_node *link;
@@ -128,7 +133,7 @@ static int dsa_port_complete(struct dsa_switch_tree *dst,
struct dsa_switch *dst_ds;
for (index = 0;; index++) {
- link = of_parse_phandle(port, "link", index);
+ link = of_parse_phandle(port->dn, "link", index);
if (!link)
break;
@@ -151,13 +156,13 @@ static int dsa_port_complete(struct dsa_switch_tree *dst,
*/
static int dsa_ds_complete(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
int err;
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (!dsa_port_is_dsa(port))
@@ -197,7 +202,7 @@ static int dsa_dst_complete(struct dsa_switch_tree *dst)
return 0;
}
-static int dsa_dsa_port_apply(struct device_node *port, u32 index,
+static int dsa_dsa_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
int err;
@@ -212,13 +217,13 @@ static int dsa_dsa_port_apply(struct device_node *port, u32 index,
return 0;
}
-static void dsa_dsa_port_unapply(struct device_node *port, u32 index,
+static void dsa_dsa_port_unapply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
dsa_cpu_dsa_destroy(port);
}
-static int dsa_cpu_port_apply(struct device_node *port, u32 index,
+static int dsa_cpu_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
int err;
@@ -235,7 +240,7 @@ static int dsa_cpu_port_apply(struct device_node *port, u32 index,
return 0;
}
-static void dsa_cpu_port_unapply(struct device_node *port, u32 index,
+static void dsa_cpu_port_unapply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
dsa_cpu_dsa_destroy(port);
@@ -243,13 +248,13 @@ static void dsa_cpu_port_unapply(struct device_node *port, u32 index,
}
-static int dsa_user_port_apply(struct device_node *port, u32 index,
+static int dsa_user_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
const char *name;
int err;
- name = of_get_property(port, "label", NULL);
+ name = of_get_property(port->dn, "label", NULL);
if (!name)
name = "eth%d";
@@ -263,7 +268,7 @@ static int dsa_user_port_apply(struct device_node *port, u32 index,
return 0;
}
-static void dsa_user_port_unapply(struct device_node *port, u32 index,
+static void dsa_user_port_unapply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
if (ds->ports[index].netdev) {
@@ -275,7 +280,7 @@ static void dsa_user_port_unapply(struct device_node *port, u32 index,
static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
int err;
@@ -309,8 +314,8 @@ static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
}
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (dsa_port_is_dsa(port)) {
@@ -337,12 +342,12 @@ static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
static void dsa_ds_unapply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (dsa_port_is_dsa(port)) {
@@ -426,7 +431,7 @@ static void dsa_dst_unapply(struct dsa_switch_tree *dst)
dst->applied = false;
}
-static int dsa_cpu_parse(struct device_node *port, u32 index,
+static int dsa_cpu_parse(struct dsa_port *port, u32 index,
struct dsa_switch_tree *dst,
struct dsa_switch *ds)
{
@@ -434,7 +439,7 @@ static int dsa_cpu_parse(struct device_node *port, u32 index,
struct net_device *ethernet_dev;
struct device_node *ethernet;
- ethernet = of_parse_phandle(port, "ethernet", 0);
+ ethernet = of_parse_phandle(port->dn, "ethernet", 0);
if (!ethernet)
return -EINVAL;
@@ -467,13 +472,13 @@ static int dsa_cpu_parse(struct device_node *port, u32 index,
static int dsa_ds_parse(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
int err;
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (dsa_port_is_cpu(port)) {
@@ -534,7 +539,7 @@ static int dsa_parse_ports_dn(struct device_node *ports, struct dsa_switch *ds)
* to have access to a correct value, just like what
* net/dsa/dsa.c::dsa_switch_setup_one does.
*/
- if (!dsa_port_is_cpu(port))
+ if (!dsa_port_is_cpu(&ds->ports[reg]))
ds->enabled_port_mask |= 1 << reg;
}
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 7e3385ec73f4..a015ec97c289 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -50,8 +50,8 @@ struct dsa_slave_priv {
/* dsa.c */
int dsa_cpu_dsa_setup(struct dsa_switch *ds, struct device *dev,
- struct device_node *port_dn, int port);
-void dsa_cpu_dsa_destroy(struct device_node *port_dn);
+ struct dsa_port *dport, int port);
+void dsa_cpu_dsa_destroy(struct dsa_port *dport);
const struct dsa_device_ops *dsa_resolve_tag_protocol(int tag_protocol);
int dsa_cpu_port_ethtool_setup(struct dsa_switch *ds);
void dsa_cpu_port_ethtool_restore(struct dsa_switch *ds);
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 01/10] net: dsa: Pass device pointer to dsa_register_switch
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
In preparation for allowing dsa_register_switch() to be supplied with
device/platform data, pass down a struct device pointer instead of a
struct device_node.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/net/dsa/b53/b53_common.c | 2 +-
drivers/net/dsa/mv88e6xxx/chip.c | 11 ++++++-----
drivers/net/dsa/qca8k.c | 2 +-
include/net/dsa.h | 2 +-
net/dsa/dsa2.c | 7 ++++---
5 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c
index 5102a3701a1a..7179eed9ee6d 100644
--- a/drivers/net/dsa/b53/b53_common.c
+++ b/drivers/net/dsa/b53/b53_common.c
@@ -1882,7 +1882,7 @@ int b53_switch_register(struct b53_device *dev)
pr_info("found switch: %s, rev %i\n", dev->name, dev->core_rev);
- return dsa_register_switch(dev->ds, dev->ds->dev->of_node);
+ return dsa_register_switch(dev->ds, dev->ds->dev);
}
EXPORT_SYMBOL(b53_switch_register);
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 987b2dbbd35a..3238a4752b98 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -4421,8 +4421,7 @@ static struct dsa_switch_driver mv88e6xxx_switch_drv = {
.ops = &mv88e6xxx_switch_ops,
};
-static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip,
- struct device_node *np)
+static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
{
struct device *dev = chip->dev;
struct dsa_switch *ds;
@@ -4437,7 +4436,7 @@ static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip,
dev_set_drvdata(dev, ds);
- return dsa_register_switch(ds, np);
+ return dsa_register_switch(ds, dev);
}
static void mv88e6xxx_unregister_switch(struct mv88e6xxx_chip *chip)
@@ -4521,9 +4520,11 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev)
if (err)
goto out_g2_irq;
- err = mv88e6xxx_register_switch(chip, np);
- if (err)
+ err = mv88e6xxx_register_switch(chip);
+ if (err) {
+ mv88e6xxx_mdio_unregister(chip);
goto out_mdio;
+ }
return 0;
diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
index 54d270d59eb0..c084aa484d2b 100644
--- a/drivers/net/dsa/qca8k.c
+++ b/drivers/net/dsa/qca8k.c
@@ -964,7 +964,7 @@ qca8k_sw_probe(struct mdio_device *mdiodev)
mutex_init(&priv->reg_mutex);
dev_set_drvdata(&mdiodev->dev, priv);
- return dsa_register_switch(priv->ds, priv->ds->dev->of_node);
+ return dsa_register_switch(priv->ds, &mdiodev->dev);
}
static void
diff --git a/include/net/dsa.h b/include/net/dsa.h
index b94d1f2ef912..16a502a6c26a 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -403,7 +403,7 @@ static inline bool dsa_uses_tagged_protocol(struct dsa_switch_tree *dst)
}
void dsa_unregister_switch(struct dsa_switch *ds);
-int dsa_register_switch(struct dsa_switch *ds, struct device_node *np);
+int dsa_register_switch(struct dsa_switch *ds, struct device *dev);
#ifdef CONFIG_PM_SLEEP
int dsa_switch_suspend(struct dsa_switch *ds);
int dsa_switch_resume(struct dsa_switch *ds);
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 42a41d84053c..4170f7ea8e28 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -579,8 +579,9 @@ static struct device_node *dsa_get_ports(struct dsa_switch *ds,
return ports;
}
-static int _dsa_register_switch(struct dsa_switch *ds, struct device_node *np)
+static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
+ struct device_node *np = dev->of_node;
struct device_node *ports = dsa_get_ports(ds, np);
struct dsa_switch_tree *dst;
u32 tree, index;
@@ -660,12 +661,12 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device_node *np)
return err;
}
-int dsa_register_switch(struct dsa_switch *ds, struct device_node *np)
+int dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
int err;
mutex_lock(&dsa2_mutex);
- err = _dsa_register_switch(ds, np);
+ err = _dsa_register_switch(ds, dev);
mutex_unlock(&dsa2_mutex);
return err;
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 00/10] net: dsa: Support for pdata in dsa2
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
Hi all,
This is not exactly new, and was sent before, although back then, I did not
have an user of the pre-declared MDIO board information, but now we do. Note
that I have additional changes queued up to have b53 register platform data for
MIPS bcm47xx and bcm63xx.
Yes I know that we should have the Orion platforms eventually be converted to
Device Tree, but until that happens, I don't want any remaining users of the
old "dsa" platform device (hence the previous DTS submissions for ARM/mvebu)
and, there will be platforms out there that most likely won't never see DT
coming their way (BCM47xx is almost 100% sure, BCM63xx maybe not in a distant
future).
We would probably want the whole series to be merged via David Miller's tree
to simplify things.
Greg, can you Ack/Nack patch 5 since it touched the core LDD?
Vivien, since some patches did change, I did not apply your Tested-by tag
to all patches.
Thanks!
Changes in v3:
- Tested EPROBE_DEFER from a mockup MDIO/DSA switch driver and everything
is fine, once the driver finally probes we have access to platform data
as expected
- added comment above dsa_port_is_valid() that port->name is mandatory
for platform data cases
- added an extra check in dsa_parse_member() for a NULL pdata pointer
- fixed a bunch of checkpatch errors and warnings
Changes in v2:
- Rebased against latest net-next/master
- Moved dev_find_class() to device_find_class() into drivers/base/core.c
- Moved dev_to_net_device into net/core/dev.c
- Utilize dsa_chip_data directly instead of dsa_platform_data
- Augmented dsa_chip_data to be multi-CPU port ready
Changes from last submission (few months back):
- rebased against latest net-next
- do not introduce dsa2_platform_data which was overkill and was meant to
allow us to do exaclty the same things with platform data and Device Tree
we use the existing dsa_platform_data instead
- properly register MDIO devices when the MDIO bus is registered and associate
platform_data with them
- add a change to the Orion platform code to demonstrate how this can be used
Thank you
Florian Fainelli (10):
net: dsa: Pass device pointer to dsa_register_switch
net: dsa: Make most functions take a dsa_port argument
net: dsa: Suffix function manipulating device_node with _dn
net: dsa: Move ports assignment closer to error checking
drivers: base: Add device_find_class()
net: dsa: Migrate to device_find_class()
net: Relocate dev_to_net_device() into core
net: dsa: Add support for platform data
net: phy: Allow pre-declaration of MDIO devices
ARM: orion: Register DSA switch as a MDIO device
arch/arm/mach-orion5x/common.c | 2 +-
arch/arm/mach-orion5x/common.h | 4 +-
arch/arm/mach-orion5x/rd88f5181l-fxo-setup.c | 7 +-
arch/arm/mach-orion5x/rd88f5181l-ge-setup.c | 7 +-
arch/arm/mach-orion5x/rd88f6183ap-ge-setup.c | 7 +-
arch/arm/mach-orion5x/wnr854t-setup.c | 2 +-
arch/arm/mach-orion5x/wrt350n-v2-setup.c | 7 +-
arch/arm/plat-orion/common.c | 25 +++-
arch/arm/plat-orion/include/plat/common.h | 4 +-
drivers/base/core.c | 19 +++
drivers/net/dsa/b53/b53_common.c | 2 +-
drivers/net/dsa/mv88e6xxx/chip.c | 11 +-
drivers/net/dsa/qca8k.c | 2 +-
drivers/net/phy/Makefile | 3 +-
drivers/net/phy/mdio-boardinfo.c | 86 +++++++++++++
drivers/net/phy/mdio-boardinfo.h | 19 +++
drivers/net/phy/mdio_bus.c | 4 +
drivers/net/phy/mdio_device.c | 11 ++
include/linux/device.h | 1 +
include/linux/mdio.h | 3 +
include/linux/mod_devicetable.h | 1 +
include/linux/netdevice.h | 2 +
include/linux/phy.h | 19 +++
include/net/dsa.h | 8 +-
net/core/dev.c | 19 +++
net/dsa/dsa.c | 53 ++------
net/dsa/dsa2.c | 174 +++++++++++++++++++--------
net/dsa/dsa_priv.h | 4 +-
28 files changed, 365 insertions(+), 141 deletions(-)
create mode 100644 drivers/net/phy/mdio-boardinfo.c
create mode 100644 drivers/net/phy/mdio-boardinfo.h
--
2.9.3
^ permalink raw reply
* [PATCH v9 3/3] iio: adc: add support for Allwinner SoCs ADC
From: Jonathan Cameron @ 2017-01-14 19:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <9e1d1e4d-84e6-ba9d-f1da-4d2bb81c2d40@free-electrons.com>
On 14 January 2017 19:19:58 GMT+00:00, Quentin Schulz <quentin.schulz@free-electrons.com> wrote:
>Hi Jonathan,
>
>On 08/01/2017 12:17, Jonathan Cameron wrote:
>> On 30/12/16 14:40, Jonathan Cameron wrote:
>>> On 13/12/16 14:33, Quentin Schulz wrote:
>>>> The Allwinner SoCs all have an ADC that can also act as a
>touchscreen
>>>> controller and a thermal sensor. This patch adds the ADC driver
>which is
>>>> based on the MFD for the same SoCs ADC.
>>>>
>>>> This also registers the thermal adc channel in the iio map array so
>>>> iio_hwmon could use it without modifying the Device Tree. This
>registers
>>>> the driver in the thermal framework.
>>>>
>>>> The thermal sensor requires the IP to be in touchscreen mode to
>return
>>>> correct values. Therefore, if the user is continuously reading the
>ADC
>>>> channel(s), the thermal framework in which the thermal sensor is
>>>> registered will switch the IP in touchscreen mode to get a
>temperature
>>>> value and requires a delay of 100ms (because of the mode
>switching),
>>>> then the ADC will switch back to ADC mode and requires also a delay
>of
>>>> 100ms. If the ADC readings are critical to user and the SoC
>temperature
>>>> is not, this driver is capable of not registering the thermal
>sensor in
>>>> the thermal framework and thus, "quicken" the ADC readings.
>>>>
>>>> This driver probes on three different platform_device_id to take
>into
>>>> account slight differences (registers bit and temperature
>computation)
>>>> between Allwinner SoCs ADCs.
>>>>
>>>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>>>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>>>> Acked-by: Jonathan Cameron <jic23@kernel.org>
>>>> Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
>>> One comment inline but not a blocker.
>>>
>>> I would ideally like an ack from the thermal side. The relevant
>code
>>> is small, but best to be sure and keep them in the loop as well.
>>>
>>> It does feel a little convoluted to have both this directly
>providing
>>> a thermal zone and being able to create one indirectly through hwmon
>as
>>> well but this solution works for me I think...
>>>
>>> Cc'd Zang and Eduardo.
>> Nothing seems to have come through on that front.
>>
>> I need to get a pull request out to Greg and rebase my tree before I
>have
>> the precursor patch in place. Give me a bump if you haven't heard
>anything by
>> the time next week.
>>
>
>Kindly "giving you a bump" you as requested since I haven't heard from
>you for a week.
Greg hasn't pulled yet, so may be a few more days.
J
>
>Thanks,
>Quentin
>
>> Thanks,
>>
>> Jonathan
>>>
>>> Jonathan
>>>> ---
>>>>
>>>> v9:
>>>> - clarify comment on why we have to use the parent node as node
>for
>>>> registering in thermal framework, (backward compatibility)
>>>> - clarify comment on why we can disable CONFIG_THERMAL_OF,
>>>> - clarify Kconfig help to say that CONFIG_THERMAL_OF can be
>disabled
>>>> but should not in most cases,
>>>> - make return value of devm_thermal_zone_of_sensor_register a
>local
>>>> variable of the condition block,
>>>> - correct scale from _PLUS_MICRO to _PLUS_NANO for ADC raw
>readings
>>>> scale,
>>>>
>>>> v8:
>>>> - remove Kconfig depends on !TOUCHSCREEN_SUN4I (moved to
>>>> MFD_SUN4I_GPADC),
>>>> - fix return values of regmap_irq_get_virq and
>platform_get_irq_byname
>>>> stored in an unsigned int and then check if negative,
>>>> - fix uninitialized ret value when an error occurs while
>registering
>>>> the thermal sensor in the framework,
>>>>
>>>> v7:
>>>> - add Kconfig depends on !TOUCHSCREEN_SUN4I,
>>>> - remove Kconfig selects THERMAL_OF,
>>>> - do not register thermal sensor if CONFIG_THERMAL_OF is disabled,
>>>> - disable irq in irq_handler rather than in read_raw,
>>>> - add delay when switching the IP's mode or channel (delay
>empirically found),
>>>> - quicken thermal sensor interrupt period,
>>>> - add masks for channel bits,
>>>> - fix deadlock in sun4i_gpadc_read if regmap_read/write fails,
>>>> - move some logic from sun4i_gpadc_read to sun4i_prepare_for_irq,
>>>> - mark last busy for runtime_pm only on success in
>sun4i_gpadc_read,
>>>> - remove cached values,
>>>> - increase wait_for_completion_timeout timeout to 1s to be sure to
>not miss the
>>>> thermal interrupt,
>>>> - add voltage scale,
>>>> - use devm_iio_device_register,
>>>>
>>>> v6:
>>>> - remove "-mfd" from filenames and variables inside MFD driver,
>>>> - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
>>>> - cosmetic changes,
>>>> - use IDs and switch over ID to get cells specific to an
>architecture, instead
>>>> of using cells direclty, in of_device_id.data,
>>>> - compute size of mfd_cells array instead of hardcoded one,
>>>>
>>>> v5:
>>>> - correct mail address,
>>>>
>>>> v4:
>>>> - rename files and variables from sunxi* to sun4i*,
>>>> - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
>>>> - remove TP in defines name,
>>>> - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
>>>> - use devm functions for regmap_add_irq_chip and mfd_add_devices,
>>>> - remove remove functions (now empty thanks to devm functions),
>>>>
>>>> v3:
>>>> - use defines in regmap_irq instead of hard coded BITs,
>>>> - use of_device_id data field to chose which MFD cells to add
>considering
>>>> the compatible responsible of the MFD probe,
>>>> - remove useless initializations,
>>>> - disable all interrupts before adding them to regmap_irqchip,
>>>> - add goto error label in probe,
>>>> - correct wrapping in header license,
>>>> - move defines from IIO driver to header,
>>>> - use GENMASK to limit the size of the variable passed to a macro,
>>>> - prefix register BIT defines with the name of the register,
>>>> - reorder defines,
>>>>
>>>> v2:
>>>> - add license headers,
>>>> - reorder alphabetically includes,
>>>> - add SUNXI_GPADC_ prefixes for defines,
>>>>
>>>> drivers/iio/adc/Kconfig | 17 ++
>>>> drivers/iio/adc/Makefile | 1 +
>>>> drivers/iio/adc/sun4i-gpadc-iio.c | 613
>++++++++++++++++++++++++++++++++++++++
>>>> include/linux/mfd/sun4i-gpadc.h | 2 +
>>>> 4 files changed, 633 insertions(+)
>>>> create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>>>
>>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>>> index 99c0514..6a6d369 100644
>>>> --- a/drivers/iio/adc/Kconfig
>>>> +++ b/drivers/iio/adc/Kconfig
>>>> @@ -434,6 +434,23 @@ config STX104
>>>> The base port addresses for the devices may be configured via
>the base
>>>> array module parameter.
>>>>
>>>> +config SUN4I_GPADC
>>>> + tristate "Support for the Allwinner SoCs GPADC"
>>>> + depends on IIO
>>>> + depends on MFD_SUN4I_GPADC
>>>> + help
>>>> + Say yes here to build support for Allwinner (A10, A13 and A31)
>SoCs
>>>> + GPADC. This ADC provides 4 channels which can be used as an ADC
>or as
>>>> + a touchscreen input and one channel for thermal sensor.
>>>> +
>>>> + The thermal sensor slows down ADC readings and can be disabled
>by
>>>> + disabling CONFIG_THERMAL_OF. However, the thermal sensor should
>be
>>>> + enabled by default since the SoC temperature is usually more
>critical
>>>> + than ADC readings.
>>>> +
>>>> + To compile this driver as a module, choose M here: the module
>will be
>>>> + called sun4i-gpadc-iio.
>>>> +
>>>> config TI_ADC081C
>>>> tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>>> depends on I2C
>>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>>> index 7a40c04..18ce8d6 100644
>>>> --- a/drivers/iio/adc/Makefile
>>>> +++ b/drivers/iio/adc/Makefile
>>>> @@ -41,6 +41,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>>>> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>>> obj-$(CONFIG_STX104) += stx104.o
>>>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>>> obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>>> obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>>> obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
>>>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c
>b/drivers/iio/adc/sun4i-gpadc-iio.c
>>>> new file mode 100644
>>>> index 0000000..a8e134f
>>>> --- /dev/null
>>>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>>>> @@ -0,0 +1,613 @@
>>>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>>>> + *
>>>> + * Copyright (c) 2016 Quentin Schulz
><quentin.schulz@free-electrons.com>
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or
>modify it under
>>>> + * the terms of the GNU General Public License version 2 as
>published by the
>>>> + * Free Software Foundation.
>>>> + *
>>>> + * The Allwinner SoCs all have an ADC that can also act as a
>touchscreen
>>>> + * controller and a thermal sensor.
>>>> + * The thermal sensor works only when the ADC acts as a
>touchscreen controller
>>>> + * and is configured to throw an interrupt every fixed periods of
>time (let say
>>>> + * every X seconds).
>>>> + * One would be tempted to disable the IP on the hardware side
>rather than
>>>> + * disabling interrupts to save some power but that resets the
>internal clock of
>>>> + * the IP, resulting in having to wait X seconds every time we
>want to read the
>>>> + * value of the thermal sensor.
>>>> + * This is also the reason of using autosuspend in pm_runtime. If
>there was no
>>>> + * autosuspend, the thermal sensor would need X seconds after
>every
>>>> + * pm_runtime_get_sync to get a value from the ADC. The
>autosuspend allows the
>>>> + * thermal sensor to be requested again in a certain time span
>before it gets
>>>> + * shutdown for not being used.
>>>> + */
>>>> +
>>>> +#include <linux/completion.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/regmap.h>
>>>> +#include <linux/thermal.h>
>>>> +#include <linux/delay.h>
>>>> +
>>>> +#include <linux/iio/iio.h>
>>>> +#include <linux/iio/driver.h>
>>>> +#include <linux/iio/machine.h>
>>>> +#include <linux/mfd/sun4i-gpadc.h>
>>>> +
>>>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>>>> +{
>>>> + return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>>> +}
>>>> +
>>>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>>>> +{
>>>> + return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>>> +}
>>>> +
>>>> +struct gpadc_data {
>>>> + int temp_offset;
>>>> + int temp_scale;
>>>> + unsigned int tp_mode_en;
>>>> + unsigned int tp_adc_select;
>>>> + unsigned int (*adc_chan_select)(unsigned int chan);
>>>> + unsigned int adc_chan_mask;
>>>> +};
>>>> +
>>>> +static const struct gpadc_data sun4i_gpadc_data = {
>>>> + .temp_offset = -1932,
>>>> + .temp_scale = 133,
>>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>>> +};
>>>> +
>>>> +static const struct gpadc_data sun5i_gpadc_data = {
>>>> + .temp_offset = -1447,
>>>> + .temp_scale = 100,
>>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>>> +};
>>>> +
>>>> +static const struct gpadc_data sun6i_gpadc_data = {
>>>> + .temp_offset = -1623,
>>>> + .temp_scale = 167,
>>>> + .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>>>> + .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>>>> + .adc_chan_select = &sun6i_gpadc_chan_select,
>>>> + .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
>>>> +};
>>>> +
>>>> +struct sun4i_gpadc_iio {
>>>> + struct iio_dev *indio_dev;
>>>> + struct completion completion;
>>>> + int temp_data;
>>>> + u32 adc_data;
>>>> + struct regmap *regmap;
>>>> + unsigned int fifo_data_irq;
>>>> + atomic_t ignore_fifo_data_irq;
>>>> + unsigned int temp_data_irq;
>>>> + atomic_t ignore_temp_data_irq;
>>>> + const struct gpadc_data *data;
>>>> + /* prevents concurrent reads of temperature and ADC */
>>>> + struct mutex mutex;
>>>> +};
>>>> +
>>>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \
>>>> + .type = IIO_VOLTAGE, \
>>>> + .indexed = 1, \
>>>> + .channel = _channel, \
>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>>>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>>>> + .datasheet_name = _name, \
>>>> +}
>>>> +
>>>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>>>> + {
>>>> + .adc_channel_label = "temp_adc",
>>>> + .consumer_dev_name = "iio_hwmon.0",
>>> It's theoretically possible we have another one of these which will
>make
>>> life interesting. Oh well, no easy way around that at the mo...
>>>> + },
>>>> + { /* sentinel */ },
>>>> +};
>>>> +
>>>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>>> + {
>>>> + .type = IIO_TEMP,
>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>>>> + BIT(IIO_CHAN_INFO_SCALE) |
>>>> + BIT(IIO_CHAN_INFO_OFFSET),
>>>> + .datasheet_name = "temp_adc",
>>>> + },
>>>> +};
>>>> +
>>>> +static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] =
>{
>>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>>> +};
>>>> +
>>>> +static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int
>channel,
>>>> + unsigned int irq)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> + int ret;
>>>> + u32 reg;
>>>> +
>>>> + pm_runtime_get_sync(indio_dev->dev.parent);
>>>> +
>>>> + reinit_completion(&info->completion);
>>>> +
>>>> + ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
>>>> + SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
>>>> + SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, ®);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + if (irq == info->fifo_data_irq) {
>>>> + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>>> + info->data->tp_mode_en |
>>>> + info->data->tp_adc_select |
>>>> + info->data->adc_chan_select(channel));
>>>> + /*
>>>> + * When the IP changes channel, it needs a bit of time to get
>>>> + * correct values.
>>>> + */
>>>> + if ((reg & info->data->adc_chan_mask) !=
>>>> + info->data->adc_chan_select(channel))
>>>> + mdelay(10);
>>>> +
>>>> + } else {
>>>> + /*
>>>> + * The temperature sensor returns valid data only when the ADC
>>>> + * operates in touchscreen mode.
>>>> + */
>>>> + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>>> + info->data->tp_mode_en);
>>>> + }
>>>> +
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + /*
>>>> + * When the IP changes mode between ADC or touchscreen, it
>>>> + * needs a bit of time to get correct values.
>>>> + */
>>>> + if ((reg & info->data->tp_adc_select) !=
>info->data->tp_adc_select)
>>>> + mdelay(100);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int
>channel, int *val,
>>>> + unsigned int irq)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> + int ret;
>>>> +
>>>> + mutex_lock(&info->mutex);
>>>> +
>>>> + ret = sun4i_prepare_for_irq(indio_dev, channel, irq);
>>>> + if (ret)
>>>> + goto err;
>>>> +
>>>> + enable_irq(irq);
>>>> +
>>>> + /*
>>>> + * The temperature sensor throws an interruption periodically
>(currently
>>>> + * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s
>delay
>>>> + * makes sure an interruption occurs in normal conditions. If it
>doesn't
>>>> + * occur, then there is a timeout.
>>>> + */
>>>> + if (!wait_for_completion_timeout(&info->completion,
>>>> + msecs_to_jiffies(1000))) {
>>>> + ret = -ETIMEDOUT;
>>>> + goto err;
>>>> + }
>>>> +
>>>> + if (irq == info->fifo_data_irq)
>>>> + *val = info->adc_data;
>>>> + else
>>>> + *val = info->temp_data;
>>>> +
>>>> + ret = 0;
>>>> + pm_runtime_mark_last_busy(indio_dev->dev.parent);
>>>> +
>>>> +err:
>>>> + pm_runtime_put_autosuspend(indio_dev->dev.parent);
>>>> + mutex_unlock(&info->mutex);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int
>channel,
>>>> + int *val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + return sun4i_gpadc_read(indio_dev, channel, val,
>info->fifo_data_irq);
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int
>*val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int
>*val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + *val = info->data->temp_offset;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int
>*val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + *val = info->data->temp_scale;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
>>>> + struct iio_chan_spec const *chan, int *val,
>>>> + int *val2, long mask)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + switch (mask) {
>>>> + case IIO_CHAN_INFO_OFFSET:
>>>> + ret = sun4i_gpadc_temp_offset(indio_dev, val);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + return IIO_VAL_INT;
>>>> + case IIO_CHAN_INFO_RAW:
>>>> + if (chan->type == IIO_VOLTAGE)
>>>> + ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
>>>> + val);
>>>> + else
>>>> + ret = sun4i_gpadc_temp_read(indio_dev, val);
>>>> +
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + return IIO_VAL_INT;
>>>> + case IIO_CHAN_INFO_SCALE:
>>>> + if (chan->type == IIO_VOLTAGE) {
>>>> + /* 3000mV / 4096 * raw */
>>>> + *val = 0;
>>>> + *val2 = 732421875;
>>>> + return IIO_VAL_INT_PLUS_NANO;
>>>> + }
>>>> +
>>>> + ret = sun4i_gpadc_temp_scale(indio_dev, val);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + return IIO_VAL_INT;
>>>> + default:
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + return -EINVAL;
>>>> +}
>>>> +
>>>> +static const struct iio_info sun4i_gpadc_iio_info = {
>>>> + .read_raw = sun4i_gpadc_read_raw,
>>>> + .driver_module = THIS_MODULE,
>>>> +};
>>>> +
>>>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void
>*dev_id)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = dev_id;
>>>> +
>>>> + if (atomic_read(&info->ignore_temp_data_irq))
>>>> + goto out;
>>>> +
>>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA,
>&info->temp_data))
>>>> + complete(&info->completion);
>>>> +
>>>> +out:
>>>> + disable_irq_nosync(info->temp_data_irq);
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void
>*dev_id)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = dev_id;
>>>> +
>>>> + if (atomic_read(&info->ignore_fifo_data_irq))
>>>> + goto out;
>>>> +
>>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_DATA,
>&info->adc_data))
>>>> + complete(&info->completion);
>>>> +
>>>> +out:
>>>> + disable_irq_nosync(info->fifo_data_irq);
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>>> +
>>>> + /* Disable the ADC on IP */
>>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
>>>> + /* Disable temperature sensor on IP */
>>>> + regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>>> +
>>>> + /* clkin = 6MHz */
>>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>>>> + SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>>>> + SUN4I_GPADC_CTRL0_FS_DIV(7) |
>>>> + SUN4I_GPADC_CTRL0_T_ACQ(63));
>>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>info->data->tp_mode_en);
>>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>>>> + SUN4I_GPADC_CTRL3_FILTER_EN |
>>>> + SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>>>> + /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s
>*/
>>>> + regmap_write(info->regmap, SUN4I_GPADC_TPR,
>>>> + SUN4I_GPADC_TPR_TEMP_ENABLE |
>>>> + SUN4I_GPADC_TPR_TEMP_PERIOD(800));
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>>>> + int val, scale, offset;
>>>> +
>>>> + if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>>>> + return -ETIMEDOUT;
>>>> +
>>>> + sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>>>> + sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>>>> +
>>>> + *temp = (val + offset) * scale;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>>>> + .get_temp = &sun4i_gpadc_get_temp,
>>>> +};
>>>> +
>>>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>>>> + .runtime_suspend = &sun4i_gpadc_runtime_suspend,
>>>> + .runtime_resume = &sun4i_gpadc_runtime_resume,
>>>> +};
>>>> +
>>>> +static int sun4i_irq_init(struct platform_device *pdev, const char
>*name,
>>>> + irq_handler_t handler, const char *devname,
>>>> + unsigned int *irq, atomic_t *atomic)
>>>> +{
>>>> + int ret;
>>>> + struct sun4i_gpadc_dev *mfd_dev =
>dev_get_drvdata(pdev->dev.parent);
>>>> + struct sun4i_gpadc_iio *info =
>iio_priv(dev_get_drvdata(&pdev->dev));
>>>> +
>>>> + /*
>>>> + * Once the interrupt is activated, the IP continuously performs
>>>> + * conversions thus throws interrupts. The interrupt is activated
>right
>>>> + * after being requested but we want to control when these
>interrupts
>>>> + * occur thus we disable it right after being requested. However,
>an
>>>> + * interrupt might occur between these two instructions and we
>have to
>>>> + * make sure that does not happen, by using atomic flags. We set
>the
>>>> + * flag before requesting the interrupt and unset it right after
>>>> + * disabling the interrupt. When an interrupt occurs between
>these two
>>>> + * instructions, reading the atomic flag will tell us to ignore
>the
>>>> + * interrupt.
>>>> + */
>>>> + atomic_set(atomic, 1);
>>>> +
>>>> + ret = platform_get_irq_byname(pdev, name);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev, "failed to get virq for irq %s\n", name);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + *irq = ret;
>>>> + ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
>>>> + devname, info);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>>>> + name, ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + disable_irq(*irq);
>>>> + atomic_set(atomic, 0);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info;
>>>> + struct iio_dev *indio_dev;
>>>> + int ret;
>>>> + struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>>>> +
>>>> + sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>>>> +
>>>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>>>> + if (!indio_dev)
>>>> + return -ENOMEM;
>>>> +
>>>> + info = iio_priv(indio_dev);
>>>> + platform_set_drvdata(pdev, indio_dev);
>>>> +
>>>> + mutex_init(&info->mutex);
>>>> + info->regmap = sun4i_gpadc_dev->regmap;
>>>> + info->indio_dev = indio_dev;
>>>> + init_completion(&info->completion);
>>>> + indio_dev->name = dev_name(&pdev->dev);
>>>> + indio_dev->dev.parent = &pdev->dev;
>>>> + indio_dev->dev.of_node = pdev->dev.of_node;
>>>> + indio_dev->info = &sun4i_gpadc_iio_info;
>>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>>> + indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>>>> + indio_dev->channels = sun4i_gpadc_channels;
>>>> +
>>>> + info->data = (struct gpadc_data
>*)platform_get_device_id(pdev)->driver_data;
>>>> +
>>>> + /*
>>>> + * Since the controller needs to be in touchscreen mode for its
>thermal
>>>> + * sensor to operate properly, and that switching between the two
>modes
>>>> + * needs a delay, always registering in the thermal framework
>will
>>>> + * significantly slow down the conversion rate of the ADCs.
>>>> + *
>>>> + * Therefore, instead of depending on THERMAL_OF in Kconfig, we
>only
>>>> + * register the sensor if that option is enabled, eventually
>leaving
>>>> + * that choice to the user.
>>>> + */
>>>> +
>>>> + if (IS_ENABLED(CONFIG_THERMAL_OF)) {
>>>> + /*
>>>> + * This driver is a child of an MFD which has a node in the DT
>>>> + * but not its children, because of DT backward compatibility
>>>> + * for A10, A13 and A31 SoCs. Therefore, the resulting devices
>>>> + * of this driver do not have an of_node variable.
>>>> + * However, its parent (the MFD driver) has an of_node variable
>>>> + * and since devm_thermal_zone_of_sensor_register uses its first
>>>> + * argument to match the phandle defined in the node of the
>>>> + * thermal driver with the of_node of the device passed as first
>>>> + * argument and the third argument to call ops from
>>>> + * thermal_zone_of_device_ops, the solution is to use the parent
>>>> + * device as first argument to match the phandle with its
>>>> + * of_node, and the device from this driver as third argument to
>>>> + * return the temperature.
>>>> + */
>>>> + struct thermal_zone_device *tzd;
>>>> + tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0,
>>>> + info,
>>>> + &sun4i_ts_tz_ops);
>>>> + if (IS_ERR(tzd)) {
>>>> + dev_err(&pdev->dev,
>>>> + "could not register thermal sensor: %ld\n",
>>>> + PTR_ERR(tzd));
>>>> + ret = PTR_ERR(tzd);
>>>> + goto err;
>>>> + }
>>>> + } else {
>>>> + indio_dev->num_channels =
>>>> + ARRAY_SIZE(sun4i_gpadc_channels_no_temp);
>>>> + indio_dev->channels = sun4i_gpadc_channels_no_temp;
>>>> + }
>>>> +
>>>> + pm_runtime_set_autosuspend_delay(&pdev->dev,
>>>> + SUN4I_GPADC_AUTOSUSPEND_DELAY);
>>>> + pm_runtime_use_autosuspend(&pdev->dev);
>>>> + pm_runtime_set_suspended(&pdev->dev);
>>>> + pm_runtime_enable(&pdev->dev);
>>>> +
>>>> + if (IS_ENABLED(CONFIG_THERMAL_OF)) {
>>>> + ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
>>>> + sun4i_gpadc_temp_data_irq_handler,
>>>> + "temp_data", &info->temp_data_irq,
>>>> + &info->ignore_temp_data_irq);
>>>> + if (ret < 0)
>>>> + goto err;
>>>> + }
>>>> +
>>>> + ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
>>>> + sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
>>>> + &info->fifo_data_irq, &info->ignore_fifo_data_irq);
>>>> + if (ret < 0)
>>>> + goto err;
>>>> +
>>>> + if (IS_ENABLED(CONFIG_THERMAL_OF)) {
>>>> + ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev,
>>>> + "failed to register iio map array\n");
>>>> + goto err;
>>>> + }
>>>> + }
>>>> +
>>>> + ret = devm_iio_device_register(&pdev->dev, indio_dev);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev, "could not register the device\n");
>>>> + goto err_map;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_map:
>>>> + if (IS_ENABLED(CONFIG_THERMAL_OF))
>>>> + iio_map_array_unregister(indio_dev);
>>>> +
>>>> +err:
>>>> + pm_runtime_put(&pdev->dev);
>>>> + pm_runtime_disable(&pdev->dev);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>>>> +{
>>>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>>> +
>>>> + pm_runtime_put(&pdev->dev);
>>>> + pm_runtime_disable(&pdev->dev);
>>>> + if (IS_ENABLED(CONFIG_THERMAL_OF))
>>>> + iio_map_array_unregister(indio_dev);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>>>> + { "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>>>> + { "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>>>> + { "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>>>> + { /* sentinel */ },
>>>> +};
>>>> +
>>>> +static struct platform_driver sun4i_gpadc_driver = {
>>>> + .driver = {
>>>> + .name = "sun4i-gpadc-iio",
>>>> + .pm = &sun4i_gpadc_pm_ops,
>>>> + },
>>>> + .id_table = sun4i_gpadc_id,
>>>> + .probe = sun4i_gpadc_probe,
>>>> + .remove = sun4i_gpadc_remove,
>>>> +};
>>>> +
>>>> +module_platform_driver(sun4i_gpadc_driver);
>>>> +
>>>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>>>> +MODULE_AUTHOR("Quentin Schulz
><quentin.schulz@free-electrons.com>");
>>>> +MODULE_LICENSE("GPL v2");
>>>> diff --git a/include/linux/mfd/sun4i-gpadc.h
>b/include/linux/mfd/sun4i-gpadc.h
>>>> index d7a29f2..509e736 100644
>>>> --- a/include/linux/mfd/sun4i-gpadc.h
>>>> +++ b/include/linux/mfd/sun4i-gpadc.h
>>>> @@ -28,6 +28,7 @@
>>>> #define SUN4I_GPADC_CTRL1_TP_MODE_EN BIT(4)
>>>> #define SUN4I_GPADC_CTRL1_TP_ADC_SELECT BIT(3)
>>>> #define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(2, 0) &
>(x))
>>>> +#define SUN4I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(2, 0)
>>>>
>>>> /* TP_CTRL1 bits for sun6i SOCs */
>>>> #define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN BIT(7)
>>>> @@ -35,6 +36,7 @@
>>>> #define SUN6I_GPADC_CTRL1_TP_MODE_EN BIT(5)
>>>> #define SUN6I_GPADC_CTRL1_TP_ADC_SELECT BIT(4)
>>>> #define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(3, 0) &
>BIT(x))
>>>> +#define SUN6I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(3, 0)
>>>>
>>>> #define SUN4I_GPADC_CTRL2 0x08
>>>>
>>>>
>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-iio"
>in
>>> the body of a message to majordomo at vger.kernel.org
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>>
>>
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
^ permalink raw reply
* [PATCH v9 3/3] iio: adc: add support for Allwinner SoCs ADC
From: Quentin Schulz @ 2017-01-14 19:19 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <f8285a90-656f-0da4-ee81-47b79ce4cd2c@kernel.org>
Hi Jonathan,
On 08/01/2017 12:17, Jonathan Cameron wrote:
> On 30/12/16 14:40, Jonathan Cameron wrote:
>> On 13/12/16 14:33, Quentin Schulz wrote:
>>> The Allwinner SoCs all have an ADC that can also act as a touchscreen
>>> controller and a thermal sensor. This patch adds the ADC driver which is
>>> based on the MFD for the same SoCs ADC.
>>>
>>> This also registers the thermal adc channel in the iio map array so
>>> iio_hwmon could use it without modifying the Device Tree. This registers
>>> the driver in the thermal framework.
>>>
>>> The thermal sensor requires the IP to be in touchscreen mode to return
>>> correct values. Therefore, if the user is continuously reading the ADC
>>> channel(s), the thermal framework in which the thermal sensor is
>>> registered will switch the IP in touchscreen mode to get a temperature
>>> value and requires a delay of 100ms (because of the mode switching),
>>> then the ADC will switch back to ADC mode and requires also a delay of
>>> 100ms. If the ADC readings are critical to user and the SoC temperature
>>> is not, this driver is capable of not registering the thermal sensor in
>>> the thermal framework and thus, "quicken" the ADC readings.
>>>
>>> This driver probes on three different platform_device_id to take into
>>> account slight differences (registers bit and temperature computation)
>>> between Allwinner SoCs ADCs.
>>>
>>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>>> Acked-by: Jonathan Cameron <jic23@kernel.org>
>>> Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
>> One comment inline but not a blocker.
>>
>> I would ideally like an ack from the thermal side. The relevant code
>> is small, but best to be sure and keep them in the loop as well.
>>
>> It does feel a little convoluted to have both this directly providing
>> a thermal zone and being able to create one indirectly through hwmon as
>> well but this solution works for me I think...
>>
>> Cc'd Zang and Eduardo.
> Nothing seems to have come through on that front.
>
> I need to get a pull request out to Greg and rebase my tree before I have
> the precursor patch in place. Give me a bump if you haven't heard anything by
> the time next week.
>
Kindly "giving you a bump" you as requested since I haven't heard from
you for a week.
Thanks,
Quentin
> Thanks,
>
> Jonathan
>>
>> Jonathan
>>> ---
>>>
>>> v9:
>>> - clarify comment on why we have to use the parent node as node for
>>> registering in thermal framework, (backward compatibility)
>>> - clarify comment on why we can disable CONFIG_THERMAL_OF,
>>> - clarify Kconfig help to say that CONFIG_THERMAL_OF can be disabled
>>> but should not in most cases,
>>> - make return value of devm_thermal_zone_of_sensor_register a local
>>> variable of the condition block,
>>> - correct scale from _PLUS_MICRO to _PLUS_NANO for ADC raw readings
>>> scale,
>>>
>>> v8:
>>> - remove Kconfig depends on !TOUCHSCREEN_SUN4I (moved to
>>> MFD_SUN4I_GPADC),
>>> - fix return values of regmap_irq_get_virq and platform_get_irq_byname
>>> stored in an unsigned int and then check if negative,
>>> - fix uninitialized ret value when an error occurs while registering
>>> the thermal sensor in the framework,
>>>
>>> v7:
>>> - add Kconfig depends on !TOUCHSCREEN_SUN4I,
>>> - remove Kconfig selects THERMAL_OF,
>>> - do not register thermal sensor if CONFIG_THERMAL_OF is disabled,
>>> - disable irq in irq_handler rather than in read_raw,
>>> - add delay when switching the IP's mode or channel (delay empirically found),
>>> - quicken thermal sensor interrupt period,
>>> - add masks for channel bits,
>>> - fix deadlock in sun4i_gpadc_read if regmap_read/write fails,
>>> - move some logic from sun4i_gpadc_read to sun4i_prepare_for_irq,
>>> - mark last busy for runtime_pm only on success in sun4i_gpadc_read,
>>> - remove cached values,
>>> - increase wait_for_completion_timeout timeout to 1s to be sure to not miss the
>>> thermal interrupt,
>>> - add voltage scale,
>>> - use devm_iio_device_register,
>>>
>>> v6:
>>> - remove "-mfd" from filenames and variables inside MFD driver,
>>> - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
>>> - cosmetic changes,
>>> - use IDs and switch over ID to get cells specific to an architecture, instead
>>> of using cells direclty, in of_device_id.data,
>>> - compute size of mfd_cells array instead of hardcoded one,
>>>
>>> v5:
>>> - correct mail address,
>>>
>>> v4:
>>> - rename files and variables from sunxi* to sun4i*,
>>> - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
>>> - remove TP in defines name,
>>> - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
>>> - use devm functions for regmap_add_irq_chip and mfd_add_devices,
>>> - remove remove functions (now empty thanks to devm functions),
>>>
>>> v3:
>>> - use defines in regmap_irq instead of hard coded BITs,
>>> - use of_device_id data field to chose which MFD cells to add considering
>>> the compatible responsible of the MFD probe,
>>> - remove useless initializations,
>>> - disable all interrupts before adding them to regmap_irqchip,
>>> - add goto error label in probe,
>>> - correct wrapping in header license,
>>> - move defines from IIO driver to header,
>>> - use GENMASK to limit the size of the variable passed to a macro,
>>> - prefix register BIT defines with the name of the register,
>>> - reorder defines,
>>>
>>> v2:
>>> - add license headers,
>>> - reorder alphabetically includes,
>>> - add SUNXI_GPADC_ prefixes for defines,
>>>
>>> drivers/iio/adc/Kconfig | 17 ++
>>> drivers/iio/adc/Makefile | 1 +
>>> drivers/iio/adc/sun4i-gpadc-iio.c | 613 ++++++++++++++++++++++++++++++++++++++
>>> include/linux/mfd/sun4i-gpadc.h | 2 +
>>> 4 files changed, 633 insertions(+)
>>> create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index 99c0514..6a6d369 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -434,6 +434,23 @@ config STX104
>>> The base port addresses for the devices may be configured via the base
>>> array module parameter.
>>>
>>> +config SUN4I_GPADC
>>> + tristate "Support for the Allwinner SoCs GPADC"
>>> + depends on IIO
>>> + depends on MFD_SUN4I_GPADC
>>> + help
>>> + Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
>>> + GPADC. This ADC provides 4 channels which can be used as an ADC or as
>>> + a touchscreen input and one channel for thermal sensor.
>>> +
>>> + The thermal sensor slows down ADC readings and can be disabled by
>>> + disabling CONFIG_THERMAL_OF. However, the thermal sensor should be
>>> + enabled by default since the SoC temperature is usually more critical
>>> + than ADC readings.
>>> +
>>> + To compile this driver as a module, choose M here: the module will be
>>> + called sun4i-gpadc-iio.
>>> +
>>> config TI_ADC081C
>>> tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>> depends on I2C
>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>> index 7a40c04..18ce8d6 100644
>>> --- a/drivers/iio/adc/Makefile
>>> +++ b/drivers/iio/adc/Makefile
>>> @@ -41,6 +41,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>>> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>> obj-$(CONFIG_STX104) += stx104.o
>>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>> obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>> obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>> obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
>>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> new file mode 100644
>>> index 0000000..a8e134f
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> @@ -0,0 +1,613 @@
>>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>>> + *
>>> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@free-electrons.com>
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify it under
>>> + * the terms of the GNU General Public License version 2 as published by the
>>> + * Free Software Foundation.
>>> + *
>>> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
>>> + * controller and a thermal sensor.
>>> + * The thermal sensor works only when the ADC acts as a touchscreen controller
>>> + * and is configured to throw an interrupt every fixed periods of time (let say
>>> + * every X seconds).
>>> + * One would be tempted to disable the IP on the hardware side rather than
>>> + * disabling interrupts to save some power but that resets the internal clock of
>>> + * the IP, resulting in having to wait X seconds every time we want to read the
>>> + * value of the thermal sensor.
>>> + * This is also the reason of using autosuspend in pm_runtime. If there was no
>>> + * autosuspend, the thermal sensor would need X seconds after every
>>> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
>>> + * thermal sensor to be requested again in a certain time span before it gets
>>> + * shutdown for not being used.
>>> + */
>>> +
>>> +#include <linux/completion.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/io.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/thermal.h>
>>> +#include <linux/delay.h>
>>> +
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/driver.h>
>>> +#include <linux/iio/machine.h>
>>> +#include <linux/mfd/sun4i-gpadc.h>
>>> +
>>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> + return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> + return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +struct gpadc_data {
>>> + int temp_offset;
>>> + int temp_scale;
>>> + unsigned int tp_mode_en;
>>> + unsigned int tp_adc_select;
>>> + unsigned int (*adc_chan_select)(unsigned int chan);
>>> + unsigned int adc_chan_mask;
>>> +};
>>> +
>>> +static const struct gpadc_data sun4i_gpadc_data = {
>>> + .temp_offset = -1932,
>>> + .temp_scale = 133,
>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>> +};
>>> +
>>> +static const struct gpadc_data sun5i_gpadc_data = {
>>> + .temp_offset = -1447,
>>> + .temp_scale = 100,
>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>> +};
>>> +
>>> +static const struct gpadc_data sun6i_gpadc_data = {
>>> + .temp_offset = -1623,
>>> + .temp_scale = 167,
>>> + .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>>> + .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>>> + .adc_chan_select = &sun6i_gpadc_chan_select,
>>> + .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
>>> +};
>>> +
>>> +struct sun4i_gpadc_iio {
>>> + struct iio_dev *indio_dev;
>>> + struct completion completion;
>>> + int temp_data;
>>> + u32 adc_data;
>>> + struct regmap *regmap;
>>> + unsigned int fifo_data_irq;
>>> + atomic_t ignore_fifo_data_irq;
>>> + unsigned int temp_data_irq;
>>> + atomic_t ignore_temp_data_irq;
>>> + const struct gpadc_data *data;
>>> + /* prevents concurrent reads of temperature and ADC */
>>> + struct mutex mutex;
>>> +};
>>> +
>>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \
>>> + .type = IIO_VOLTAGE, \
>>> + .indexed = 1, \
>>> + .channel = _channel, \
>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>>> + .datasheet_name = _name, \
>>> +}
>>> +
>>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>>> + {
>>> + .adc_channel_label = "temp_adc",
>>> + .consumer_dev_name = "iio_hwmon.0",
>> It's theoretically possible we have another one of these which will make
>> life interesting. Oh well, no easy way around that at the mo...
>>> + },
>>> + { /* sentinel */ },
>>> +};
>>> +
>>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>> + {
>>> + .type = IIO_TEMP,
>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>>> + BIT(IIO_CHAN_INFO_SCALE) |
>>> + BIT(IIO_CHAN_INFO_OFFSET),
>>> + .datasheet_name = "temp_adc",
>>> + },
>>> +};
>>> +
>>> +static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] = {
>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>> +};
>>> +
>>> +static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel,
>>> + unsigned int irq)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> + int ret;
>>> + u32 reg;
>>> +
>>> + pm_runtime_get_sync(indio_dev->dev.parent);
>>> +
>>> + reinit_completion(&info->completion);
>>> +
>>> + ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
>>> + SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
>>> + SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, ®);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (irq == info->fifo_data_irq) {
>>> + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>> + info->data->tp_mode_en |
>>> + info->data->tp_adc_select |
>>> + info->data->adc_chan_select(channel));
>>> + /*
>>> + * When the IP changes channel, it needs a bit of time to get
>>> + * correct values.
>>> + */
>>> + if ((reg & info->data->adc_chan_mask) !=
>>> + info->data->adc_chan_select(channel))
>>> + mdelay(10);
>>> +
>>> + } else {
>>> + /*
>>> + * The temperature sensor returns valid data only when the ADC
>>> + * operates in touchscreen mode.
>>> + */
>>> + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>>> + info->data->tp_mode_en);
>>> + }
>>> +
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /*
>>> + * When the IP changes mode between ADC or touchscreen, it
>>> + * needs a bit of time to get correct values.
>>> + */
>>> + if ((reg & info->data->tp_adc_select) != info->data->tp_adc_select)
>>> + mdelay(100);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
>>> + unsigned int irq)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> + int ret;
>>> +
>>> + mutex_lock(&info->mutex);
>>> +
>>> + ret = sun4i_prepare_for_irq(indio_dev, channel, irq);
>>> + if (ret)
>>> + goto err;
>>> +
>>> + enable_irq(irq);
>>> +
>>> + /*
>>> + * The temperature sensor throws an interruption periodically (currently
>>> + * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s delay
>>> + * makes sure an interruption occurs in normal conditions. If it doesn't
>>> + * occur, then there is a timeout.
>>> + */
>>> + if (!wait_for_completion_timeout(&info->completion,
>>> + msecs_to_jiffies(1000))) {
>>> + ret = -ETIMEDOUT;
>>> + goto err;
>>> + }
>>> +
>>> + if (irq == info->fifo_data_irq)
>>> + *val = info->adc_data;
>>> + else
>>> + *val = info->temp_data;
>>> +
>>> + ret = 0;
>>> + pm_runtime_mark_last_busy(indio_dev->dev.parent);
>>> +
>>> +err:
>>> + pm_runtime_put_autosuspend(indio_dev->dev.parent);
>>> + mutex_unlock(&info->mutex);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
>>> + int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + *val = info->data->temp_offset;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + *val = info->data->temp_scale;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
>>> + struct iio_chan_spec const *chan, int *val,
>>> + int *val2, long mask)
>>> +{
>>> + int ret;
>>> +
>>> + switch (mask) {
>>> + case IIO_CHAN_INFO_OFFSET:
>>> + ret = sun4i_gpadc_temp_offset(indio_dev, val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return IIO_VAL_INT;
>>> + case IIO_CHAN_INFO_RAW:
>>> + if (chan->type == IIO_VOLTAGE)
>>> + ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
>>> + val);
>>> + else
>>> + ret = sun4i_gpadc_temp_read(indio_dev, val);
>>> +
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return IIO_VAL_INT;
>>> + case IIO_CHAN_INFO_SCALE:
>>> + if (chan->type == IIO_VOLTAGE) {
>>> + /* 3000mV / 4096 * raw */
>>> + *val = 0;
>>> + *val2 = 732421875;
>>> + return IIO_VAL_INT_PLUS_NANO;
>>> + }
>>> +
>>> + ret = sun4i_gpadc_temp_scale(indio_dev, val);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return IIO_VAL_INT;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + return -EINVAL;
>>> +}
>>> +
>>> +static const struct iio_info sun4i_gpadc_iio_info = {
>>> + .read_raw = sun4i_gpadc_read_raw,
>>> + .driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> + struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> + if (atomic_read(&info->ignore_temp_data_irq))
>>> + goto out;
>>> +
>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
>>> + complete(&info->completion);
>>> +
>>> +out:
>>> + disable_irq_nosync(info->temp_data_irq);
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> + struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> + if (atomic_read(&info->ignore_fifo_data_irq))
>>> + goto out;
>>> +
>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
>>> + complete(&info->completion);
>>> +
>>> +out:
>>> + disable_irq_nosync(info->fifo_data_irq);
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> + /* Disable the ADC on IP */
>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
>>> + /* Disable temperature sensor on IP */
>>> + regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> + /* clkin = 6MHz */
>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>>> + SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>>> + SUN4I_GPADC_CTRL0_FS_DIV(7) |
>>> + SUN4I_GPADC_CTRL0_T_ACQ(63));
>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>>> + SUN4I_GPADC_CTRL3_FILTER_EN |
>>> + SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>>> + /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */
>>> + regmap_write(info->regmap, SUN4I_GPADC_TPR,
>>> + SUN4I_GPADC_TPR_TEMP_ENABLE |
>>> + SUN4I_GPADC_TPR_TEMP_PERIOD(800));
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>>> +{
>>> + struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>>> + int val, scale, offset;
>>> +
>>> + if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>>> + return -ETIMEDOUT;
>>> +
>>> + sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>>> + sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>>> +
>>> + *temp = (val + offset) * scale;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>>> + .get_temp = &sun4i_gpadc_get_temp,
>>> +};
>>> +
>>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>>> + .runtime_suspend = &sun4i_gpadc_runtime_suspend,
>>> + .runtime_resume = &sun4i_gpadc_runtime_resume,
>>> +};
>>> +
>>> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
>>> + irq_handler_t handler, const char *devname,
>>> + unsigned int *irq, atomic_t *atomic)
>>> +{
>>> + int ret;
>>> + struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
>>> +
>>> + /*
>>> + * Once the interrupt is activated, the IP continuously performs
>>> + * conversions thus throws interrupts. The interrupt is activated right
>>> + * after being requested but we want to control when these interrupts
>>> + * occur thus we disable it right after being requested. However, an
>>> + * interrupt might occur between these two instructions and we have to
>>> + * make sure that does not happen, by using atomic flags. We set the
>>> + * flag before requesting the interrupt and unset it right after
>>> + * disabling the interrupt. When an interrupt occurs between these two
>>> + * instructions, reading the atomic flag will tell us to ignore the
>>> + * interrupt.
>>> + */
>>> + atomic_set(atomic, 1);
>>> +
>>> + ret = platform_get_irq_byname(pdev, name);
>>> + if (ret < 0) {
>>> + dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>>> + return ret;
>>> + }
>>> +
>>> + ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret);
>>> + if (ret < 0) {
>>> + dev_err(&pdev->dev, "failed to get virq for irq %s\n", name);
>>> + return ret;
>>> + }
>>> +
>>> + *irq = ret;
>>> + ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
>>> + devname, info);
>>> + if (ret < 0) {
>>> + dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>>> + name, ret);
>>> + return ret;
>>> + }
>>> +
>>> + disable_irq(*irq);
>>> + atomic_set(atomic, 0);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>>> +{
>>> + struct sun4i_gpadc_iio *info;
>>> + struct iio_dev *indio_dev;
>>> + int ret;
>>> + struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>>> +
>>> + sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>>> +
>>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>>> + if (!indio_dev)
>>> + return -ENOMEM;
>>> +
>>> + info = iio_priv(indio_dev);
>>> + platform_set_drvdata(pdev, indio_dev);
>>> +
>>> + mutex_init(&info->mutex);
>>> + info->regmap = sun4i_gpadc_dev->regmap;
>>> + info->indio_dev = indio_dev;
>>> + init_completion(&info->completion);
>>> + indio_dev->name = dev_name(&pdev->dev);
>>> + indio_dev->dev.parent = &pdev->dev;
>>> + indio_dev->dev.of_node = pdev->dev.of_node;
>>> + indio_dev->info = &sun4i_gpadc_iio_info;
>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>> + indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>>> + indio_dev->channels = sun4i_gpadc_channels;
>>> +
>>> + info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
>>> +
>>> + /*
>>> + * Since the controller needs to be in touchscreen mode for its thermal
>>> + * sensor to operate properly, and that switching between the two modes
>>> + * needs a delay, always registering in the thermal framework will
>>> + * significantly slow down the conversion rate of the ADCs.
>>> + *
>>> + * Therefore, instead of depending on THERMAL_OF in Kconfig, we only
>>> + * register the sensor if that option is enabled, eventually leaving
>>> + * that choice to the user.
>>> + */
>>> +
>>> + if (IS_ENABLED(CONFIG_THERMAL_OF)) {
>>> + /*
>>> + * This driver is a child of an MFD which has a node in the DT
>>> + * but not its children, because of DT backward compatibility
>>> + * for A10, A13 and A31 SoCs. Therefore, the resulting devices
>>> + * of this driver do not have an of_node variable.
>>> + * However, its parent (the MFD driver) has an of_node variable
>>> + * and since devm_thermal_zone_of_sensor_register uses its first
>>> + * argument to match the phandle defined in the node of the
>>> + * thermal driver with the of_node of the device passed as first
>>> + * argument and the third argument to call ops from
>>> + * thermal_zone_of_device_ops, the solution is to use the parent
>>> + * device as first argument to match the phandle with its
>>> + * of_node, and the device from this driver as third argument to
>>> + * return the temperature.
>>> + */
>>> + struct thermal_zone_device *tzd;
>>> + tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0,
>>> + info,
>>> + &sun4i_ts_tz_ops);
>>> + if (IS_ERR(tzd)) {
>>> + dev_err(&pdev->dev,
>>> + "could not register thermal sensor: %ld\n",
>>> + PTR_ERR(tzd));
>>> + ret = PTR_ERR(tzd);
>>> + goto err;
>>> + }
>>> + } else {
>>> + indio_dev->num_channels =
>>> + ARRAY_SIZE(sun4i_gpadc_channels_no_temp);
>>> + indio_dev->channels = sun4i_gpadc_channels_no_temp;
>>> + }
>>> +
>>> + pm_runtime_set_autosuspend_delay(&pdev->dev,
>>> + SUN4I_GPADC_AUTOSUSPEND_DELAY);
>>> + pm_runtime_use_autosuspend(&pdev->dev);
>>> + pm_runtime_set_suspended(&pdev->dev);
>>> + pm_runtime_enable(&pdev->dev);
>>> +
>>> + if (IS_ENABLED(CONFIG_THERMAL_OF)) {
>>> + ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
>>> + sun4i_gpadc_temp_data_irq_handler,
>>> + "temp_data", &info->temp_data_irq,
>>> + &info->ignore_temp_data_irq);
>>> + if (ret < 0)
>>> + goto err;
>>> + }
>>> +
>>> + ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
>>> + sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
>>> + &info->fifo_data_irq, &info->ignore_fifo_data_irq);
>>> + if (ret < 0)
>>> + goto err;
>>> +
>>> + if (IS_ENABLED(CONFIG_THERMAL_OF)) {
>>> + ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
>>> + if (ret < 0) {
>>> + dev_err(&pdev->dev,
>>> + "failed to register iio map array\n");
>>> + goto err;
>>> + }
>>> + }
>>> +
>>> + ret = devm_iio_device_register(&pdev->dev, indio_dev);
>>> + if (ret < 0) {
>>> + dev_err(&pdev->dev, "could not register the device\n");
>>> + goto err_map;
>>> + }
>>> +
>>> + return 0;
>>> +
>>> +err_map:
>>> + if (IS_ENABLED(CONFIG_THERMAL_OF))
>>> + iio_map_array_unregister(indio_dev);
>>> +
>>> +err:
>>> + pm_runtime_put(&pdev->dev);
>>> + pm_runtime_disable(&pdev->dev);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>>> +{
>>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> +
>>> + pm_runtime_put(&pdev->dev);
>>> + pm_runtime_disable(&pdev->dev);
>>> + if (IS_ENABLED(CONFIG_THERMAL_OF))
>>> + iio_map_array_unregister(indio_dev);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>>> + { "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>>> + { "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>>> + { "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>>> + { /* sentinel */ },
>>> +};
>>> +
>>> +static struct platform_driver sun4i_gpadc_driver = {
>>> + .driver = {
>>> + .name = "sun4i-gpadc-iio",
>>> + .pm = &sun4i_gpadc_pm_ops,
>>> + },
>>> + .id_table = sun4i_gpadc_id,
>>> + .probe = sun4i_gpadc_probe,
>>> + .remove = sun4i_gpadc_remove,
>>> +};
>>> +
>>> +module_platform_driver(sun4i_gpadc_driver);
>>> +
>>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>>> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
>>> +MODULE_LICENSE("GPL v2");
>>> diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
>>> index d7a29f2..509e736 100644
>>> --- a/include/linux/mfd/sun4i-gpadc.h
>>> +++ b/include/linux/mfd/sun4i-gpadc.h
>>> @@ -28,6 +28,7 @@
>>> #define SUN4I_GPADC_CTRL1_TP_MODE_EN BIT(4)
>>> #define SUN4I_GPADC_CTRL1_TP_ADC_SELECT BIT(3)
>>> #define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(2, 0) & (x))
>>> +#define SUN4I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(2, 0)
>>>
>>> /* TP_CTRL1 bits for sun6i SOCs */
>>> #define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN BIT(7)
>>> @@ -35,6 +36,7 @@
>>> #define SUN6I_GPADC_CTRL1_TP_MODE_EN BIT(5)
>>> #define SUN6I_GPADC_CTRL1_TP_ADC_SELECT BIT(4)
>>> #define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(3, 0) & BIT(x))
>>> +#define SUN6I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(3, 0)
>>>
>>> #define SUN4I_GPADC_CTRL2 0x08
>>>
>>>
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
>> the body of a message to majordomo at vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
--
Quentin Schulz, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply
* [PATCH net-next v2 08/10] net: dsa: Add support for platform data
From: Florian Fainelli @ 2017-01-14 19:12 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <173cffe2-f407-ac43-c5ee-85a70576dd36@gmail.com>
On 01/13/2017 02:37 PM, Florian Fainelli wrote:
> On 01/13/2017 06:04 AM, Andrew Lunn wrote:
>>> index cd91070b5467..d326fc4afad7 100644
>>> --- a/net/dsa/dsa2.c
>>> +++ b/net/dsa/dsa2.c
>>> @@ -81,17 +81,23 @@ static void dsa_dst_del_ds(struct dsa_switch_tree *dst,
>>>
>>> static bool dsa_port_is_valid(struct dsa_port *port)
>>> {
>>> - return !!port->dn;
>>> + return !!(port->dn || port->name);
>>> }
>>
>> Does this clash with Viviens recent change to make names optional and
>> have the kernel assign it?
>
> So there were two ways to look at this, one was that could check here
> that ds->pd is assigned and port->name is assigned, which means that
> platform data has to provide valid port name. We can also eliminate this
> check entirely because we now support NULL names just fines.
Considering that the comment above struct dsa_chip_data::port_names in
net/dsa/dsa.h is pretty clear about the port_names usage, I am tempted
to keep the code as-is since without a name, for platform data, we would
not have a way to tell if a port is disabled or not.
Does that work for you?
--
Florian
^ permalink raw reply
* [PATCH] reset: uniphier: add compatible string for LD11 SD-reset block
From: Masahiro Yamada @ 2017-01-14 19:04 UTC (permalink / raw)
To: linux-arm-kernel
The LD11 SoC is equipped with not only MIO-reset but also SD-reset
for controlling RST_n pin of the eMMC device.
Update the binding document and remove unneeded "." from each line
in itemization.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
---
.../devicetree/bindings/reset/uniphier-reset.txt | 47 +++++++++++-----------
drivers/reset/reset-uniphier.c | 4 ++
2 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/Documentation/devicetree/bindings/reset/uniphier-reset.txt b/Documentation/devicetree/bindings/reset/uniphier-reset.txt
index 5020524..83ab0f5 100644
--- a/Documentation/devicetree/bindings/reset/uniphier-reset.txt
+++ b/Documentation/devicetree/bindings/reset/uniphier-reset.txt
@@ -6,14 +6,14 @@ System reset
Required properties:
- compatible: should be one of the following:
- "socionext,uniphier-sld3-reset" - for sLD3 SoC.
- "socionext,uniphier-ld4-reset" - for LD4 SoC.
- "socionext,uniphier-pro4-reset" - for Pro4 SoC.
- "socionext,uniphier-sld8-reset" - for sLD8 SoC.
- "socionext,uniphier-pro5-reset" - for Pro5 SoC.
- "socionext,uniphier-pxs2-reset" - for PXs2/LD6b SoC.
- "socionext,uniphier-ld11-reset" - for LD11 SoC.
- "socionext,uniphier-ld20-reset" - for LD20 SoC.
+ "socionext,uniphier-sld3-reset" - for sLD3 SoC
+ "socionext,uniphier-ld4-reset" - for LD4 SoC
+ "socionext,uniphier-pro4-reset" - for Pro4 SoC
+ "socionext,uniphier-sld8-reset" - for sLD8 SoC
+ "socionext,uniphier-pro5-reset" - for Pro5 SoC
+ "socionext,uniphier-pxs2-reset" - for PXs2/LD6b SoC
+ "socionext,uniphier-ld11-reset" - for LD11 SoC
+ "socionext,uniphier-ld20-reset" - for LD20 SoC
- #reset-cells: should be 1.
Example:
@@ -37,14 +37,15 @@ Media I/O (MIO) reset, SD reset
Required properties:
- compatible: should be one of the following:
- "socionext,uniphier-sld3-mio-reset" - for sLD3 SoC.
- "socionext,uniphier-ld4-mio-reset" - for LD4 SoC.
- "socionext,uniphier-pro4-mio-reset" - for Pro4 SoC.
- "socionext,uniphier-sld8-mio-reset" - for sLD8 SoC.
- "socionext,uniphier-pro5-sd-reset" - for Pro5 SoC.
- "socionext,uniphier-pxs2-sd-reset" - for PXs2/LD6b SoC.
- "socionext,uniphier-ld11-mio-reset" - for LD11 SoC.
- "socionext,uniphier-ld20-sd-reset" - for LD20 SoC.
+ "socionext,uniphier-sld3-mio-reset" - for sLD3 SoC
+ "socionext,uniphier-ld4-mio-reset" - for LD4 SoC
+ "socionext,uniphier-pro4-mio-reset" - for Pro4 SoC
+ "socionext,uniphier-sld8-mio-reset" - for sLD8 SoC
+ "socionext,uniphier-pro5-sd-reset" - for Pro5 SoC
+ "socionext,uniphier-pxs2-sd-reset" - for PXs2/LD6b SoC
+ "socionext,uniphier-ld11-mio-reset" - for LD11 SoC (MIO)
+ "socionext,uniphier-ld11-sd-reset" - for LD11 SoC (SD)
+ "socionext,uniphier-ld20-sd-reset" - for LD20 SoC
- #reset-cells: should be 1.
Example:
@@ -68,13 +69,13 @@ Peripheral reset
Required properties:
- compatible: should be one of the following:
- "socionext,uniphier-ld4-peri-reset" - for LD4 SoC.
- "socionext,uniphier-pro4-peri-reset" - for Pro4 SoC.
- "socionext,uniphier-sld8-peri-reset" - for sLD8 SoC.
- "socionext,uniphier-pro5-peri-reset" - for Pro5 SoC.
- "socionext,uniphier-pxs2-peri-reset" - for PXs2/LD6b SoC.
- "socionext,uniphier-ld11-peri-reset" - for LD11 SoC.
- "socionext,uniphier-ld20-peri-reset" - for LD20 SoC.
+ "socionext,uniphier-ld4-peri-reset" - for LD4 SoC
+ "socionext,uniphier-pro4-peri-reset" - for Pro4 SoC
+ "socionext,uniphier-sld8-peri-reset" - for sLD8 SoC
+ "socionext,uniphier-pro5-peri-reset" - for Pro5 SoC
+ "socionext,uniphier-pxs2-peri-reset" - for PXs2/LD6b SoC
+ "socionext,uniphier-ld11-peri-reset" - for LD11 SoC
+ "socionext,uniphier-ld20-peri-reset" - for LD20 SoC
- #reset-cells: should be 1.
Example:
diff --git a/drivers/reset/reset-uniphier.c b/drivers/reset/reset-uniphier.c
index 968c3ae..9c11be3 100644
--- a/drivers/reset/reset-uniphier.c
+++ b/drivers/reset/reset-uniphier.c
@@ -390,6 +390,10 @@ static const struct of_device_id uniphier_reset_match[] = {
.data = uniphier_sld3_mio_reset_data,
},
{
+ .compatible = "socionext,uniphier-ld11-sd-reset",
+ .data = uniphier_pro5_sd_reset_data,
+ },
+ {
.compatible = "socionext,uniphier-ld20-sd-reset",
.data = uniphier_pro5_sd_reset_data,
},
--
2.7.4
^ permalink raw reply related
* [PATCH net-next v2 05/10] drivers: base: Add device_find_class()
From: Florian Fainelli @ 2017-01-14 19:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <063D6719AE5E284EB5DD2968C1650D6DB02620D3@AcuExch.aculab.com>
On 01/13/2017 02:55 AM, David Laight wrote:
> From: Florian Fainelli
>> Sent: 12 January 2017 22:51
>> On 01/12/2017 01:21 PM, David Miller wrote:
>>> From: Florian Fainelli <f.fainelli@gmail.com>
>>> Date: Wed, 11 Jan 2017 19:41:16 -0800
>>>
>>>> Add a helper function to lookup a device reference given a class name.
>>>> This is a preliminary patch to remove adhoc code from net/dsa/dsa.c and
>>>> make it more generic.
> ...
>>>> +static int dev_is_class(struct device *dev, void *class)
>>>
>>> I know you are just moving code, but this class argumnet is a string
>>> and thus should be "char *" or even "const char *".
>>
>> Well, this is really so that we don't need to cast the arguments passed
>> to device_find_child(), which takes a void *data as well. If we made
>> that a const char *class, we'd get warnings that look like these:
>>
>> drivers/base/core.c: In function 'device_find_class':
>> drivers/base/core.c:2083:2: warning: passing argument 2 of
>> 'device_find_child' discards 'const' qualifier from pointer target type
>> [enabled by default]
>> return device_find_child(parent, class, dev_is_class);
>> ^
>> drivers/base/core.c:2050:16: note: expected 'void *' but argument is of
>> type 'const char *'
>> struct device *device_find_child(struct device *parent, void *data,
>> ^
> ...
>
> Maybe device_find_child() needs changing to take 'const void *' ?
As a separate patch set, sure, I will add that to my TODO. Thanks!
--
Florian
^ permalink raw reply
* [BISECT] ARM build errors on GCC v6.2 (crypto: arm/aes - replace scalar AES cipher)
From: Krzysztof Kozlowski @ 2017-01-14 18:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114182011.GX14217@n2100.armlinux.org.uk>
On Sat, Jan 14, 2017 at 06:20:12PM +0000, Russell King - ARM Linux wrote:
> On Sat, Jan 14, 2017 at 04:24:35PM +0200, Krzysztof Kozlowski wrote:
> > Hi,
> >
> > allyesconfig and multi_v7_defconfig fail to build on recent linux-next
> > on GCC 6.2.0.
> >
> > Errors:
> > ../arch/arm/crypto/aes-cipher-core.S: Assembler messages:
> > ../arch/arm/crypto/aes-cipher-core.S:21: Error: selected processor does not support `tt .req ip' in ARM mode
> > ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movw tt,#:lower16:crypto_ft_tab'
> > ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movt tt,#:upper16:crypto_ft_tab'
> >
> > Compiler: arm-linux-gnueabi-gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005
>
> It's worth pointing out that _assembler_ messages come from _binutils_
> rather than _gcc_. Please advise which version of _binutils_ you
> are using. Thanks.
Ah, yes, so the binutils 2.27 (Ubuntu package: 2.27-8ubuntu2).
However Ard mentioned that this is already fixed.
Best regards,
Krzysztof
^ permalink raw reply
* [BISECT] ARM build errors on GCC v6.2 (crypto: arm/aes - replace scalar AES cipher)
From: Russell King - ARM Linux @ 2017-01-14 18:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114142435.ashdj2xm5b3kzg2p@kozik-lap>
On Sat, Jan 14, 2017 at 04:24:35PM +0200, Krzysztof Kozlowski wrote:
> Hi,
>
> allyesconfig and multi_v7_defconfig fail to build on recent linux-next
> on GCC 6.2.0.
>
> Errors:
> ../arch/arm/crypto/aes-cipher-core.S: Assembler messages:
> ../arch/arm/crypto/aes-cipher-core.S:21: Error: selected processor does not support `tt .req ip' in ARM mode
> ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movw tt,#:lower16:crypto_ft_tab'
> ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movt tt,#:upper16:crypto_ft_tab'
>
> Compiler: arm-linux-gnueabi-gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005
It's worth pointing out that _assembler_ messages come from _binutils_
rather than _gcc_. Please advise which version of _binutils_ you
are using. Thanks.
--
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.
^ permalink raw reply
* [PATCH RFC] mmc: mxs-mmc: Implement CMD23 support
From: Stefan Wahren @ 2017-01-14 18:10 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAOMZO5Czy17FytjyHP_pHHqCetDiYyOHaVOFu5hZHjAD=8rHUA@mail.gmail.com>
Hi Fabio,
> Fabio Estevam <festevam@gmail.com> hat am 14. Januar 2017 um 18:40 geschrieben:
>
>
> Hi Stefan,
>
> On Sat, Jan 14, 2017 at 3:29 PM, Stefan Wahren <stefan.wahren@i2se.com> wrote:
> > This patch implements support for multiblock transfers bounded
> > by SET_BLOCK_COUNT (CMD23) on the MXS MMC host driver.
> >
> > Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
>
> Just curious: what is the throughput gain you notice with this patch?
unfortunately i didn't see any noticeable changes on throughput.
>
> Thanks
^ permalink raw reply
* [PATCH 3/4] iio: adc: add a driver for the SAR ADC found in Amlogic Meson SoCs
From: Martin Blumenstingl @ 2017-01-14 17:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <870f8899-b3a1-153a-5953-88ac23ff6942@kernel.org>
Hi Jonathan,
thank you for the review!
(further comments from me inline)
I think I'll send an updated version on Monday.
On Sat, Jan 14, 2017 at 3:46 PM, Jonathan Cameron <jic23@kernel.org> wrote:
> On 11/01/17 17:43, Martin Blumenstingl wrote:
>> This adds support for the SAR (Successive Approximation Register) ADC
>> on the Amlogic Meson SoCs.
>>
>> The code is based on the public S805 (Meson8b) and S905 (GXBB)
>> datasheets, as well as by reading (various versions of) the vendor
>> driver and by inspecting the registers on the vendor kernels of my
>> testing-hardware.
>>
>> Currently the GXBB, GXL and GXM SoCs are supported. GXBB hardware has
>> 10-bit ADC resolution, while GXL and GXM have 12-bit ADC resolution.
>> The code was written to support older SoCs (Meson8 and Meson8b) as well,
>> but due to lack of actual testing-hardware no of_device_id was added for
>> these.
>>
>> Two "features" from the vendor driver are currently missing:
>> - the vendor driver uses channel #7 for calibration (this improves the
>> accuracy of the results - in my tests the results were less than 3%
>> off without calibration compared to the vendor driver). Adding support
>> for this should be easy, but is not required for most applications.
>> - channel #6 is connected to the SoCs internal temperature sensor.
>> Adding support for this is probably not so easy since (based on the
>> u-boot sources) most SoC versions are using different registers and
>> algorithms for the conversion from "ADC value" to temperature.
>>
>> Supported by the hardware but currently not supported by the driver:
>> - reading multiple channels at the same time (the hardware has a FIFO
>> buffer which stores multiple results)
>> - continuous sampling (this would require a way to enable this
>> individually because otherwise the ADC would be drawing power
>> constantly)
>> - interrupt support (similar to the vendor driver this new driver is
>> polling the results. It is unclear if the IRQ-mode is supported on
>> older (Meson6 or Meson8) hardware as well or if there are any errata)
>>
> Russell Cc'd for a quick question on the clk api.
a quick side-note the clk API: my driver is a clock consumer and
provider at the same time. This seems to be a recurring pattern in
Amlogic hardware designs (as the MMC and DWMAC glue drivers are doing
this also), see [0]
> Ideally include a source for datasheets if available. Saves time googling and
> perhaps getting the wrong thing!
OK, will do this in v2
> A few other minor comments inline. Pretty good V1.
thanks :-)
> Jonathan
>> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
>> ---
>> drivers/iio/adc/Kconfig | 12 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/meson_saradc.c | 860 +++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 873 insertions(+)
>> create mode 100644 drivers/iio/adc/meson_saradc.c
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 9c8b558ba19e..86059b9b91bf 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -371,6 +371,18 @@ config MEN_Z188_ADC
>> This driver can also be built as a module. If so, the module will be
>> called men_z188_adc.
>>
>> +config MESON_SARADC
>> + tristate "Amlogic Meson SAR ADC driver"
>> + default ARCH_MESON
>> + depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST)
>> + select REGMAP_MMIO
>> + help
>> + Say yes here to build support for the SAR ADC found in Amlogic Meson
>> + SoCs.
>> +
>> + To compile this driver as a module, choose M here: the
>> + module will be called meson_saradc.
>> +
>> config MXS_LRADC
>> tristate "Freescale i.MX23/i.MX28 LRADC"
>> depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index d36c4be8d1fc..de05b9e75f8f 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -36,6 +36,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
>> obj-$(CONFIG_MCP3422) += mcp3422.o
>> obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o
>> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
>> +obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
>> obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o
>> obj-$(CONFIG_NAU7802) += nau7802.o
>> obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>> diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
>> new file mode 100644
>> index 000000000000..06e8ac620385
>> --- /dev/null
>> +++ b/drivers/iio/adc/meson_saradc.c
>> @@ -0,0 +1,860 @@
>> +/*
>> + * Amlogic Meson Successive Approximation Register (SAR) A/D Converter
>> + *
>> + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/delay.h>
>> +#include <linux/reset.h>
>> +#include <linux/regmap.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#define SAR_ADC_REG0 0x00
>> + #define SAR_ADC_REG0_PANEL_DETECT BIT(31)
>> + #define SAR_ADC_REG0_BUSY_MASK GENMASK(30, 28)
>> + #define SAR_ADC_REG0_DELTA_BUSY BIT(30)
>> + #define SAR_ADC_REG0_AVG_BUSY BIT(29)
>> + #define SAR_ADC_REG0_SAMPLE_BUSY BIT(28)
>> + #define SAR_ADC_REG0_FIFO_FULL BIT(27)
>> + #define SAR_ADC_REG0_FIFO_EMPTY BIT(26)
>> + #define SAR_ADC_REG0_FIFO_COUNT_MASK GENMASK(25, 21)
>> + #define SAR_ADC_REG0_ADC_BIAS_CTRL_MASK GENMASK(20, 19)
>> + #define SAR_ADC_REG0_CURR_CHAN_ID_MASK GENMASK(18, 16)
>> + #define SAR_ADC_REG0_ADC_TEMP_SEN_SEL BIT(15)
>> + #define SAR_ADC_REG0_SAMPLING_STOP BIT(14)
>> + #define SAR_ADC_REG0_CHAN_DELTA_EN_MASK GENMASK(13, 12)
>> + #define SAR_ADC_REG0_DETECT_IRQ_POL BIT(10)
>> + #define SAR_ADC_REG0_DETECT_IRQ_EN BIT(9)
>> + #define SAR_ADC_REG0_FIFO_CNT_IRQ_MASK GENMASK(8, 4)
>> + #define SAR_ADC_REG0_FIFO_IRQ_EN BIT(3)
>> + #define SAR_ADC_REG0_SAMPLING_START BIT(2)
>> + #define SAR_ADC_REG0_CONTINUOUS_EN BIT(1)
>> + #define SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE BIT(0)
>> +
>> +#define SAR_ADC_CHAN_LIST 0x04
>> + #define SAR_ADC_CHAN_LIST_MAX_INDEX_MASK GENMASK(26, 24)
>> + #define SAR_ADC_CHAN_CHAN_ENTRY_MASK(_chan) \
>> + (GENMASK(2, 0) << (_chan * 3))
>> +
>> +#define SAR_ADC_AVG_CNTL 0x08
>> + #define SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(_chan) \
>> + (16 + (_chan * 2))
>> + #define SAR_ADC_AVG_CNTL_AVG_MODE_MASK(_chan) \
>> + (GENMASK(17, 16) << (_chan * 2))
>> + #define SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(_chan) \
>> + (0 + (_chan * 2))
>> + #define SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(_chan) \
>> + (GENMASK(1, 0) << (_chan * 2))
>> +
>> +#define SAR_ADC_REG3 0x0c
>> + #define SAR_ADC_REG3_CNTL_USE_SC_DLY BIT(31)
>> + #define SAR_ADC_REG3_CLK_EN BIT(30)
>> + #define SAR_ADC_REG3_BL30_INITIALIZED BIT(28)
>> + #define SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN BIT(27)
>> + #define SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE BIT(26)
>> + #define SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK GENMASK(25, 23)
>> + #define SAR_ADC_REG3_DETECT_EN BIT(22)
>> + #define SAR_ADC_REG3_ADC_EN BIT(21)
>> + #define SAR_ADC_REG3_PANEL_DETECT_COUNT_MASK GENMASK(20, 18)
>> + #define SAR_ADC_REG3_PANEL_DETECT_FILTER_TB_MASK GENMASK(17, 16)
>> + #define SAR_ADC_REG3_ADC_CLK_DIV_SHIFT 10
>> + #define SAR_ADC_REG3_ADC_CLK_DIV_WIDTH 5
>> + #define SAR_ADC_REG3_ADC_CLK_DIV_MASK GENMASK(15, 10)
>> + #define SAR_ADC_REG3_BLOCK_DLY_SEL_MASK GENMASK(9, 8)
>> + #define SAR_ADC_REG3_BLOCK_DLY_MASK GENMASK(7, 0)
>> +
>> +#define SAR_ADC_DELAY 0x10
>> + #define SAR_ADC_DELAY_INPUT_DLY_SEL_MASK GENMASK(25, 24)
>> + #define SAR_ADC_DELAY_BL30_BUSY BIT(15)
>> + #define SAR_ADC_DELAY_KERNEL_BUSY BIT(14)
>> + #define SAR_ADC_DELAY_INPUT_DLY_CNT_MASK GENMASK(23, 16)
>> + #define SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK GENMASK(9, 8)
>> + #define SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK GENMASK(7, 0)
>> +
>> +#define SAR_ADC_LAST_RD 0x14
>> + #define SAR_ADC_LAST_RD_LAST_CHANNEL1_MASK GENMASK(23, 16)
>> + #define SAR_ADC_LAST_RD_LAST_CHANNEL0_MASK GENMASK(9, 0)
>> +
>> +#define SAR_ADC_FIFO_RD 0x18
>> + #define SAR_ADC_FIFO_RD_CHAN_ID_MASK GENMASK(14, 12)
>> + #define SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK GENMASK(11, 0)
>> +
>> +#define SAR_ADC_AUX_SW 0x1c
>> + #define SAR_ADC_AUX_SW_MUX_SEL_CHAN_MASK(_chan) \
>> + (GENMASK(10, 8) << ((_chan - 2) * 2))
>> + #define SAR_ADC_AUX_SW_VREF_P_MUX BIT(6)
>> + #define SAR_ADC_AUX_SW_VREF_N_MUX BIT(5)
>> + #define SAR_ADC_AUX_SW_MODE_SEL BIT(4)
>> + #define SAR_ADC_AUX_SW_YP_DRIVE_SW BIT(3)
>> + #define SAR_ADC_AUX_SW_XP_DRIVE_SW BIT(2)
>> + #define SAR_ADC_AUX_SW_YM_DRIVE_SW BIT(1)
>> + #define SAR_ADC_AUX_SW_XM_DRIVE_SW BIT(0)
>> +
>> +#define SAR_ADC_CHAN_10_SW 0x20
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK GENMASK(25, 23)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_VREF_P_MUX BIT(22)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_VREF_N_MUX BIT(21)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_MODE_SEL BIT(20)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW BIT(19)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW BIT(18)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_YM_DRIVE_SW BIT(17)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_XM_DRIVE_SW BIT(16)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK GENMASK(9, 7)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_VREF_P_MUX BIT(6)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_VREF_N_MUX BIT(5)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_MODE_SEL BIT(4)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW BIT(3)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW BIT(2)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_YM_DRIVE_SW BIT(1)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_XM_DRIVE_SW BIT(0)
>> +
>> +#define SAR_ADC_DETECT_IDLE_SW 0x24
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_SW_EN BIT(26)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK GENMASK(25, 23)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_P_MUX BIT(22)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_N_MUX BIT(21)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_SEL BIT(20)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YP_DRIVE_SW BIT(19)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XP_DRIVE_SW BIT(18)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YM_DRIVE_SW BIT(17)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XM_DRIVE_SW BIT(16)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK GENMASK(9, 7)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_P_MUX BIT(6)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_N_MUX BIT(5)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_SEL BIT(4)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YP_DRIVE_SW BIT(3)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XP_DRIVE_SW BIT(2)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YM_DRIVE_SW BIT(1)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XM_DRIVE_SW BIT(0)
>> +
>> +#define SAR_ADC_DELTA_10 0x28
>> + #define SAR_ADC_DELTA_10_TEMP_SEL BIT(27)
>> + #define SAR_ADC_DELTA_10_TS_REVE1 BIT(26)
>> + #define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_SHIFT 16
>> + #define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_MASK GENMASK(25, 16)
>> + #define SAR_ADC_DELTA_10_TS_REVE0 BIT(15)
>> + #define SAR_ADC_DELTA_10_TS_C_SHIFT 11
>> + #define SAR_ADC_DELTA_10_TS_C_MASK GENMASK(14, 11)
>> + #define SAR_ADC_DELTA_10_TS_VBG_EN BIT(10)
>> + #define SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_SHIFT 0
>> + #define SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_MASK GENMASK(9, 0)
>> +
>> +/* NOTE: registers from here are undocumented (the vendor Linux kernel driver
>> + * and u-boot source served as reference). These only seem to be relevant on
>> + * GXBB and newer.
>> + */
>> +#define SAR_ADC_REG11 0x2c
>> + #define SAR_ADC_REG11_BANDGAP_EN BIT(13)
>> +
>> +#define SAR_ADC_REG13 0x34
>> + #define SAR_ADC_REG13_12BIT_CALIBRATION_MASK GENMASK(13, 8)
>> +
>> +#define SAR_ADC_MAX_FIFO_SIZE 32
>> +#define SAR_ADC_NUM_CHANNELS ARRAY_SIZE(meson_saradc_iio_channels)
>> +#define SAR_ADC_VALUE_MASK(_priv) (BIT(_priv->resolution) - 1)
>> +
>> +#define MESON_SAR_ADC_CHAN(_chan, _type) { \
>> + .type = _type, \
>> + .indexed = true, \
>> + .channel = _chan, \
>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
>> + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>> + .datasheet_name = "SAR_ADC_CH"#_chan, \
>> +}
>> +
>> +/* TODO: the hardware supports IIO_TEMP for channel 6 as well which is
> Multline comment syntax.
I got this wrong in 2 other places as well. will be fixed in v2, thanks!
>> + * currently not supported by this driver.
>> + */
>> +static const struct iio_chan_spec meson_saradc_iio_channels[] = {
>> + MESON_SAR_ADC_CHAN(0, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(1, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(2, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(3, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(4, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(5, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(6, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(7, IIO_VOLTAGE),
>> + IIO_CHAN_SOFT_TIMESTAMP(8),
>> +};
>> +
>> +enum meson_saradc_avg_mode {
>> + NO_AVERAGING = 0x0,
>> + MEAN_AVERAGING = 0x1,
>> + MEDIAN_AVERAGING = 0x2,
>> +};
>> +
>> +enum meson_saradc_num_samples {
>> + ONE_SAMPLE = 0x0,
>> + TWO_SAMPLES = 0x1,
>> + FOUR_SAMPLES = 0x2,
>> + EIGHT_SAMPLES = 0x3,
>> +};
>> +
>> +enum meson_saradc_chan7_mux_sel {
>> + CHAN7_MUX_VSS = 0x0,
>> + CHAN7_MUX_VDD_DIV4 = 0x1,
>> + CHAN7_MUX_VDD_DIV2 = 0x2,
>> + CHAN7_MUX_VDD_MUL3_DIV4 = 0x3,
>> + CHAN7_MUX_VDD = 0x4,
>> + CHAN7_MUX_CH7_INPUT = 0x7,
>> +};
>> +
>> +struct meson_saradc_priv {
>> + struct regmap *regmap;
>> + struct clk *clkin;
>> + struct clk *core_clk;
>> + struct clk *sana_clk;
>> + struct clk *adc_sel_clk;
>> + struct clk *adc_clk;
>> + struct clk_gate clk_gate;
>> + struct clk *adc_div_clk;
>> + struct clk_divider clk_div;
>> + struct regulator *vref;
>> + struct completion completion;
>> + u8 resolution;
>> +};
>> +
>> +static const struct regmap_config meson_saradc_regmap_config = {
>> + .reg_bits = 8,
>> + .val_bits = 32,
>> + .reg_stride = 4,
>> + .max_register = SAR_ADC_REG13,
>> +};
>> +
>> +static unsigned int meson_saradc_get_fifo_count(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 regval;
>> +
>> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
>> +
>> + return FIELD_GET(SAR_ADC_REG0_FIFO_COUNT_MASK, regval);
>> +}
>> +
>> +static int meson_saradc_wait_busy_clear(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int regval, timeout = 10000;
>> +
>> + do {
>> + udelay(1);
>> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
>> + } while (FIELD_GET(SAR_ADC_REG0_BUSY_MASK, regval) && timeout--);
>> +
>> + if (timeout < 0)
>> + return -ETIMEDOUT;
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_read_raw_sample(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + int *val)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int ret, regval, fifo_chan, fifo_val, sum = 0, count = 0;
>> +
>> + ret = meson_saradc_wait_busy_clear(indio_dev);
>> + if (ret)
>> + return ret;
>> +
>> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
>> +
>> + while (meson_saradc_get_fifo_count(indio_dev) > 0 &&
>> + count < SAR_ADC_MAX_FIFO_SIZE) {
>> + regmap_read(priv->regmap, SAR_ADC_FIFO_RD, ®val);
>> +
>> + fifo_chan = FIELD_GET(SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval);
>> + if (fifo_chan == chan->channel) {
>> + fifo_val = FIELD_GET(SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK,
>> + regval) & SAR_ADC_VALUE_MASK(priv);
>> + sum += fifo_val;
>> + count++;
>> + }
>> + }
>> +
>> + if (!count)
>> + return -ENOENT;
>> +
>> + *val = sum / count;
>> +
>> + return 0;
>> +}
>> +
>> +static void meson_saradc_set_averaging(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + enum meson_saradc_avg_mode mode,
>> + enum meson_saradc_num_samples samples)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 val;
>> +
>> + val = samples << SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
>> + SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(chan->channel),
>> + val);
>> +
>> + val = mode << SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
>> + SAR_ADC_AVG_CNTL_AVG_MODE_MASK(chan->channel), val);
>> +}
>> +
>> +static void meson_saradc_enable_channel(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 regval;
>> +
>> + /* the SAR ADC engine allows sampling multiple channels at the same
>> + * time. to keep it simple we're only working with one *internal*
>> + * channel, which starts counting at index 0 (which means: count = 1).
>> + */
>> + regval = FIELD_PREP(SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0);
>> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
>> + SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval);
>> +
>> + /* map channel index 0 to the channel which we want to read */
>> + regval = FIELD_PREP(SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
>> + SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), regval);
>> +
>> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
>> + chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
>> + SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
>> + regval);
>> +
>> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
>> + chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
>> + SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
>> + regval);
>> +
>> + if (chan->channel == 6)
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELTA_10,
>> + SAR_ADC_DELTA_10_TEMP_SEL, 0);
>> +}
>> +
>> +static void meson_saradc_set_channel7_mux(struct iio_dev *indio_dev,
>> + enum meson_saradc_chan7_mux_sel sel)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 regval;
>> +
>> + regval = FIELD_PREP(SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
>> + SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval);
>> +
>> + usleep_range(10, 20);
>> +}
>> +
>> +static void meson_saradc_start_sample_engine(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE,
>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLING_START,
>> + SAR_ADC_REG0_SAMPLING_START);
>> +}
>> +
>> +static void meson_saradc_stop_sample_engine(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLING_STOP,
>> + SAR_ADC_REG0_SAMPLING_STOP);
>> +
>> + /* wait until all modules are stopped */
>> + meson_saradc_wait_busy_clear(indio_dev);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, 0);
>> +}
>> +
>> +static void meson_saradc_lock(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int val;
>> +
>> + mutex_lock(&indio_dev->mlock);
>> +
>> + /* prevent BL30 from using the SAR ADC while we are using it */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_KERNEL_BUSY,
>> + SAR_ADC_DELAY_KERNEL_BUSY);
>> +
>> + /* wait until BL30 releases it's lock (so we can use the SAR ADC) */
>> + do {
>> + udelay(1);
>> + regmap_read(priv->regmap, SAR_ADC_DELAY, &val);
>> + } while (val & SAR_ADC_DELAY_BL30_BUSY);
>> +}
>> +
>> +static void meson_saradc_unlock(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + /* allow BL30 to use the SAR ADC again */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_KERNEL_BUSY, 0);
>> +
>> + mutex_unlock(&indio_dev->mlock);
>> +}
>> +
>> +static int meson_saradc_get_sample(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + enum meson_saradc_avg_mode avg_mode,
>> + enum meson_saradc_num_samples avg_samples,
>> + int *val)
>> +{
>> + int ret, tmp;
>> +
>> + meson_saradc_lock(indio_dev);
>> +
>> + /* clear old values from the FIFO buffer, ignoring errors */
>> + meson_saradc_read_raw_sample(indio_dev, chan, &tmp);
>> +
>> + meson_saradc_set_averaging(indio_dev, chan, avg_mode, avg_samples);
>> +
>> + meson_saradc_enable_channel(indio_dev, chan);
>> +
>> + meson_saradc_start_sample_engine(indio_dev);
>> + ret = meson_saradc_read_raw_sample(indio_dev, chan, val);
>> + meson_saradc_stop_sample_engine(indio_dev);
>> +
>> + meson_saradc_unlock(indio_dev);
>> +
>> + if (ret) {
>> + dev_warn(&indio_dev->dev,
>> + "failed to read sample for channel %d: %d\n",
>> + chan->channel, ret);
>> + return ret;
>> + }
>> +
>> + return IIO_VAL_INT;
>> +}
>> +
>> +static int meson_saradc_iio_info_read_raw(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + int *val, int *val2, long mask)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int ret;
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + return meson_saradc_get_sample(indio_dev, chan, NO_AVERAGING,
>> + ONE_SAMPLE, val);
>> + break;
>> +
>> + case IIO_CHAN_INFO_AVERAGE_RAW:
>> + return meson_saradc_get_sample(indio_dev, chan, MEAN_AVERAGING,
>> + EIGHT_SAMPLES, val);
>> + break;
>> +
>> + case IIO_CHAN_INFO_SCALE:
>> + ret = regulator_get_voltage(priv->vref);
>> + if (ret < 0) {
>> + dev_err(&indio_dev->dev,
>> + "failed to get vref voltage: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + *val = ret / 1000;
>> + *val2 = priv->resolution;
>> + return IIO_VAL_FRACTIONAL_LOG2;
>> +
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>> +
>> +static int meson_saradc_clk_init(struct iio_dev *indio_dev, void __iomem *base)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + struct clk_init_data init;
>> + char clk_name[32];
>> + const char *clk_parents[1];
>> +
>> + snprintf(clk_name, sizeof(clk_name), "%s#adc_div",
>> + of_node_full_name(indio_dev->dev.of_node));
>> + init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
>> + init.flags = 0;
>> + init.ops = &clk_divider_ops;
>> + clk_parents[0] = __clk_get_name(priv->clkin);
>> + init.parent_names = clk_parents;
>> + init.num_parents = 1;
>> +
>> + priv->clk_div.reg = base + SAR_ADC_REG3;
>> + priv->clk_div.shift = SAR_ADC_REG3_ADC_CLK_DIV_SHIFT;
>> + priv->clk_div.width = SAR_ADC_REG3_ADC_CLK_DIV_WIDTH;
>> + priv->clk_div.hw.init = &init;
>> + priv->clk_div.flags = 0;
>> +
>> + priv->adc_div_clk = devm_clk_register(&indio_dev->dev,
>> + &priv->clk_div.hw);
>> + if (WARN_ON(IS_ERR(priv->adc_div_clk)))
>> + return PTR_ERR(priv->adc_div_clk);
>> +
>> + snprintf(clk_name, sizeof(clk_name), "%s#adc_en",
>> + of_node_full_name(indio_dev->dev.of_node));
>> + init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
>> + init.flags = CLK_SET_RATE_PARENT;
>> + init.ops = &clk_gate_ops;
>> + clk_parents[0] = __clk_get_name(priv->adc_div_clk);
>> + init.parent_names = clk_parents;
>> + init.num_parents = 1;
>> +
>> + priv->clk_gate.reg = base + SAR_ADC_REG3;
>> + priv->clk_gate.bit_idx = fls(SAR_ADC_REG3_CLK_EN);
>> + priv->clk_gate.hw.init = &init;
>> +
>> + priv->adc_clk = devm_clk_register(&indio_dev->dev, &priv->clk_gate.hw);
>> + if (WARN_ON(IS_ERR(priv->adc_clk)))
>> + return PTR_ERR(priv->adc_clk);
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_init(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int regval, ret;
>> +
>> + /* make sure we start at CH7 input */
> why? Seems like a little more detail would be good here ;)
I'll change this to "make sure we start at CH7 input since the other
muxes are only used for internal calibration." in v2
>> + meson_saradc_set_channel7_mux(indio_dev, CHAN7_MUX_CH7_INPUT);
>> +
>> + regmap_read(priv->regmap, SAR_ADC_REG3, ®val);
>> + if (regval & SAR_ADC_REG3_BL30_INITIALIZED) {
>> + dev_info(&indio_dev->dev, "already initialized by BL30\n");
>> + return 0;
>> + }
>> +
>> + dev_info(&indio_dev->dev, "initializing SAR ADC\n");
> I'd argue this provides no useful info so should be dropped.
> Useful for debugging no doubt, but just noise going forward.
do you want me to remove them or should I turn them into dev_dbg() (so
they can be enabled for debugging purposes)?
>> +
>> + meson_saradc_stop_sample_engine(indio_dev);
>> +
>> + /* update the channel 6 MUX to select the temperature sensor */
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
>> + SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
>> +
>> + /* disable all channels by default */
>> + regmap_write(priv->regmap, SAR_ADC_CHAN_LIST, 0x0);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
>> + SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE, 0);
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
>> + SAR_ADC_REG3_CNTL_USE_SC_DLY,
>> + SAR_ADC_REG3_CNTL_USE_SC_DLY);
>> +
>> + /* delay between two samples = (10+1) * 1uS */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK, 10));
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, 0));
>> +
>> + /* delay between two samples = (10+1) * 1uS */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, 10));
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_INPUT_DLY_SEL_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, 1));
>> +
> Cool. I hadn't come across FIELD_PREP before. Neater and tidier than having
> a shift and a mask for at least some usecases.
I think these were introduced with v4.9. I like them because I tend
use GENMASK() incorrectly and with those macros I get an error at
compile-time (without having to debug my code at all)
>> + ret = clk_set_parent(priv->adc_sel_clk, priv->clkin);
>> + if (ret) {
>> + dev_err(&indio_dev->dev,
>> + "failed to set adc parent to clkin\n");
>> + return ret;
>> + }
>> +
>> + ret = clk_set_rate(priv->adc_clk, 1200000);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to set adc clock rate\n");
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_hw_enable(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int ret;
>> +
>> + meson_saradc_lock(indio_dev);
>> +
>> + ret = regulator_enable(priv->vref);
>> + if (ret < 0) {
>> + dev_err(&indio_dev->dev, "failed to enable vref regulator\n");
>> + goto err_vref;
>> + }
>> +
>> + ret = clk_prepare_enable(priv->core_clk);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to enable core clk\n");
>> + goto err_core_clk;
>> + }
>> +
>> + ret = clk_prepare_enable(priv->sana_clk);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to enable sana clk\n");
>> + goto err_sana_clk;
>> + }
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG11,
>> + SAR_ADC_REG11_BANDGAP_EN, SAR_ADC_REG11_BANDGAP_EN);
> Is this controlling an offset for a bandgap or some actual electronics?
> Not sure if it should be disabled on error and the datasheets I've found are
> far from great! You disable it in the disable, so I'd expect it to be
> unwound on error in here too.
actually the bandgap is not documented at all :(
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN,
>> + SAR_ADC_REG3_ADC_EN);
> The fact you turn this of in the disable suggests to me that on error
> you should be doing it in here too.
I will disable this along with SAR_ADC_REG11_BANDGAP_EN in the
err_adc_clk label, thanks for spotting this.
>> +
>> + udelay(5);
>> +
>> + ret = clk_prepare_enable(priv->adc_clk);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to enable adc_en clk\n");
>> + goto err_adc_clk;
>> + }
>> +
>> + meson_saradc_unlock(indio_dev);
>> +
>> + return 0;
>> +
>> +err_adc_clk:
>> + clk_disable_unprepare(priv->sana_clk);
>> +err_sana_clk:
>> + clk_disable_unprepare(priv->core_clk);
>> +err_core_clk:
>> + regulator_disable(priv->vref);
>> +err_vref:
>> + meson_saradc_unlock(indio_dev);
>> + return ret;
>> +}
>> +
>> +static void meson_saradc_hw_disable(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + meson_saradc_lock(indio_dev);
>> +
>> + clk_disable_unprepare(priv->adc_clk);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN, 0);
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG11,
>> + SAR_ADC_REG11_BANDGAP_EN, 0);
>> +
>> + clk_disable_unprepare(priv->sana_clk);
>> + clk_disable_unprepare(priv->core_clk);
>> +
>> + regulator_disable(priv->vref);
>> +
>> + meson_saradc_unlock(indio_dev);
>> +}
>> +
>> +static const struct iio_info meson_saradc_iio_info = {
>> + .read_raw = meson_saradc_iio_info_read_raw,
>> + .driver_module = THIS_MODULE,
>> +};
>> +
>> +static const struct of_device_id meson_saradc_of_match[] = {
>> + {
>> + .compatible = "amlogic,meson-gxbb-saradc",
>> + .data = (void *)10,
> Might have been worth having a structure array indexed from an enum.
> For now it is overkill, but seems likely there are a few other differences
> that aren't supported yet?
what do you mean with "structure array indexed from an enum"? I can
introduce some match-specific struct if you want (just like it's done
in rockchip_saradc.c with "struct rockchip_saradc_data").
>> + }, {
>> + .compatible = "amlogic,meson-gxl-saradc",
>> + .data = (void *)12,
>> + },
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, meson_saradc_of_match);
>> +
>> +static int meson_saradc_probe(struct platform_device *pdev)
>> +{
>> + struct meson_saradc_priv *priv;
>> + struct iio_dev *indio_dev;
>> + struct resource *res;
>> + void __iomem *base;
>> + const struct of_device_id *match;
>> + int ret;
>> +
>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
>> + if (!indio_dev) {
>> + dev_err(&pdev->dev, "failed allocating iio device\n");
>> + return -ENOMEM;
>> + }
>> +
>> + priv = iio_priv(indio_dev);
>> +
>> + match = of_match_device(meson_saradc_of_match, &pdev->dev);
>> + priv->resolution = (unsigned long)match->data;
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + base = devm_ioremap_resource(&pdev->dev, res);
>> + if (IS_ERR(base))
>> + return PTR_ERR(base);
>> +
>> + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
>> + &meson_saradc_regmap_config);
>> + if (IS_ERR(priv->regmap))
>> + return PTR_ERR(priv->regmap);
>> +
>> + init_completion(&priv->completion);
>> +
>> + priv->clkin = devm_clk_get(&pdev->dev, "clkin");
>> + if (IS_ERR(priv->clkin)) {
>> + dev_err(&pdev->dev, "failed to get clkin\n");
>> + return PTR_ERR(priv->clkin);
>> + }
>> +
>> + priv->core_clk = devm_clk_get(&pdev->dev, "core");
>> + if (IS_ERR(priv->core_clk)) {
>> + dev_err(&pdev->dev, "failed to get core clk\n");
>> + return PTR_ERR(priv->core_clk);
>> + }
>> +
>> + priv->sana_clk = devm_clk_get(&pdev->dev, "sana");
> Oh for a devm_clk_get_optional to handle this boiler plate neatly.
> It's been suggested before, but nothing seems to have come of it.
I guess quite a few drivers would benefit from that. maybe we should
take this to the linux-clk list again?
> Some array clk get functions might also clean this up a touch.
yes, unfortunately in this case it's not that easy as it would have to
allow a mix of mandatory and optional clocks. Additionally I cannot
bulk-enable them unconditionally since some of these are simple gates,
others need to be reparented and for some the rate has to be set.
>> + if (IS_ERR(priv->sana_clk)) {
>> + if (PTR_ERR(priv->sana_clk) == -ENOENT) {
>> + priv->sana_clk = NULL;
>> + } else {
>> + dev_err(&pdev->dev, "failed to get sana clk\n");
>> + return PTR_ERR(priv->sana_clk);
>> + }
>> + }
>> +
>> + priv->adc_clk = devm_clk_get(&pdev->dev, "adc_clk");
>> + if (IS_ERR(priv->adc_clk)) {
>> + if (PTR_ERR(priv->adc_clk) == -ENOENT) {
>> + priv->adc_clk = NULL;
>> + } else {
>> + dev_err(&pdev->dev, "failed to get adc clk\n");
>> + return PTR_ERR(priv->adc_clk);
>> + }
>> + }
>> +
>> + priv->adc_sel_clk = devm_clk_get(&pdev->dev, "adc_sel");
>> + if (IS_ERR(priv->adc_sel_clk)) {
>> + if (PTR_ERR(priv->adc_sel_clk) == -ENOENT) {
>> + priv->adc_sel_clk = NULL;
>> + } else {
>> + dev_err(&pdev->dev, "failed to get adc_sel clk\n");
>> + return PTR_ERR(priv->adc_sel_clk);
>> + }
>> + }
>> +
>> + /* on pre-GXBB SoCs the SAR ADC itself provides the ADC clock: */
>> + if (!priv->adc_clk) {
>> + ret = meson_saradc_clk_init(indio_dev, base);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + priv->vref = devm_regulator_get(&pdev->dev, "vref");
>> + if (IS_ERR(priv->vref)) {
>> + dev_err(&pdev->dev, "failed to get vref regulator\n");
>> + return PTR_ERR(priv->vref);
>> + }
>> +
>> + ret = meson_saradc_init(indio_dev);
>> + if (ret)
>> + goto err;
>> +
>> + ret = meson_saradc_hw_enable(indio_dev);
>> + if (ret)
>> + goto err;
>> +
>> + platform_set_drvdata(pdev, indio_dev);
>> +
>> + indio_dev->name = dev_name(&pdev->dev);
>> + indio_dev->dev.parent = &pdev->dev;
>> + indio_dev->dev.of_node = pdev->dev.of_node;
>> + indio_dev->modes = INDIO_DIRECT_MODE;
>> + indio_dev->info = &meson_saradc_iio_info;
>> +
>> + indio_dev->channels = meson_saradc_iio_channels;
>> + indio_dev->num_channels = SAR_ADC_NUM_CHANNELS;
>> +
>> + ret = iio_device_register(indio_dev);
>> + if (ret)
>> + goto err_hw;
>> +
>> + return 0;
>> +
>> +err_hw:
>> + meson_saradc_hw_disable(indio_dev);
>> +err:
>> + return ret;
>> +}
>> +
>> +static int meson_saradc_remove(struct platform_device *pdev)
>> +{
>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +
>> + meson_saradc_hw_disable(indio_dev);
>> + iio_device_unregister(indio_dev);
>> +
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int meson_saradc_suspend(struct device *dev)
>> +{
>> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
>> +
>> + meson_saradc_hw_disable(indio_dev);
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_resume(struct device *dev)
>> +{
>> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
>> +
>> + return meson_saradc_hw_enable(indio_dev);
>> +}
>> +#endif /* CONFIG_PM_SLEEP */
>> +
>> +static SIMPLE_DEV_PM_OPS(meson_saradc_pm_ops,
>> + meson_saradc_suspend, meson_saradc_resume);
>> +
>> +static struct platform_driver meson_saradc_driver = {
>> + .probe = meson_saradc_probe,
>> + .remove = meson_saradc_remove,
>> + .driver = {
>> + .name = "meson-saradc",
>> + .of_match_table = meson_saradc_of_match,
>> + .pm = &meson_saradc_pm_ops,
>> + },
>> +};
>> +
>> +module_platform_driver(meson_saradc_driver);
>> +
>> +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
>> +MODULE_DESCRIPTION("Amlogic Meson SAR ADC driver");
>> +MODULE_LICENSE("GPL v2");
>>
>
Regards,
Martin
[0] http://lists.infradead.org/pipermail/linux-amlogic/2016-August/000986.html
^ permalink raw reply
* [PATCH RFC] mmc: mxs-mmc: Implement CMD23 support
From: Fabio Estevam @ 2017-01-14 17:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1484414958-2665-1-git-send-email-stefan.wahren@i2se.com>
Hi Stefan,
On Sat, Jan 14, 2017 at 3:29 PM, Stefan Wahren <stefan.wahren@i2se.com> wrote:
> This patch implements support for multiblock transfers bounded
> by SET_BLOCK_COUNT (CMD23) on the MXS MMC host driver.
>
> Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
Just curious: what is the throughput gain you notice with this patch?
Thanks
^ permalink raw reply
* [PATCH RFC] mmc: mxs-mmc: Implement CMD23 support
From: Stefan Wahren @ 2017-01-14 17:29 UTC (permalink / raw)
To: linux-arm-kernel
This patch implements support for multiblock transfers bounded
by SET_BLOCK_COUNT (CMD23) on the MXS MMC host driver.
Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
---
drivers/mmc/host/mxs-mmc.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c
index c8b8ac6..add1e70 100644
--- a/drivers/mmc/host/mxs-mmc.c
+++ b/drivers/mmc/host/mxs-mmc.c
@@ -153,7 +153,11 @@ static void mxs_mmc_request_done(struct mxs_mmc_host *host)
}
}
- if (data) {
+ if (cmd == mrq->sbc) {
+ /* Finished CMD23, now send actual command. */
+ mxs_mmc_start_cmd(host, mrq->cmd);
+ return;
+ } else if (data) {
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
data->sg_len, ssp->dma_dir);
/*
@@ -166,7 +170,7 @@ static void mxs_mmc_request_done(struct mxs_mmc_host *host)
data->bytes_xfered = 0;
host->data = NULL;
- if (mrq->stop) {
+ if (data->stop && (data->error || !mrq->sbc)) {
mxs_mmc_start_cmd(host, mrq->stop);
return;
}
@@ -495,7 +499,11 @@ static void mxs_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
WARN_ON(host->mrq != NULL);
host->mrq = mrq;
- mxs_mmc_start_cmd(host, mrq->cmd);
+
+ if (mrq->sbc)
+ mxs_mmc_start_cmd(host, mrq->sbc);
+ else
+ mxs_mmc_start_cmd(host, mrq->cmd);
}
static void mxs_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@@ -642,7 +650,7 @@ static int mxs_mmc_probe(struct platform_device *pdev)
/* set mmc core parameters */
mmc->ops = &mxs_mmc_ops;
mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
- MMC_CAP_SDIO_IRQ | MMC_CAP_NEEDS_POLL;
+ MMC_CAP_SDIO_IRQ | MMC_CAP_NEEDS_POLL | MMC_CAP_CMD23;
host->broken_cd = of_property_read_bool(np, "broken-cd");
--
1.7.9.5
^ permalink raw reply related
* [PATCH v2 5/6] arm: dts: mt2701: Add ethernet device node.
From: Sean Wang @ 2017-01-14 16:10 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <f9f5616f-a327-be4d-3d1d-4555e67d123a@phrozen.org>
Hi John,
the watchdog driver should just be the driver that includes
reset functions called from driver and then send reset signal
to abnormal hw..
however luckily ETHDMA_RST provided from watchdog is not required
and even actually the latest driver didn't refer to the property
no longer. So i will remove it from dtsi in the next version
Sean
On Sat, 2017-01-14 at 11:32 +0100, John Crispin wrote:
> Hi Erin,
>
> small comment inline
>
> On 13/01/2017 09:42, Erin Lo wrote:
> > From: Sean Wang <sean.wang@mediatek.com>
> >
> > Add ethernet device node for MT2701.
> >
> > Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> > Signed-off-by: Erin Lo <erin.lo@mediatek.com>
> > ---
> > arch/arm/boot/dts/mt2701-evb.dts | 40 ++++++++++++++++++++++++++++++++++++++++
> > arch/arm/boot/dts/mt2701.dtsi | 22 ++++++++++++++++++++++
> > 2 files changed, 62 insertions(+)
> >
> > diff --git a/arch/arm/boot/dts/mt2701-evb.dts b/arch/arm/boot/dts/mt2701-evb.dts
> > index a483798..40abd3b 100644
> > --- a/arch/arm/boot/dts/mt2701-evb.dts
> > +++ b/arch/arm/boot/dts/mt2701-evb.dts
> > @@ -28,7 +28,47 @@
> > status = "okay";
> > };
> >
> > +ð {
> > + mac-address = [00 00 00 00 00 00];
> > + status = "okay";
> > + pinctrl-names = "default";
> > + pinctrl-0 = <&gmac1_pins>;
> > + gmac1: mac at 1 {
> > + compatible = "mediatek,eth-mac";
> > + reg = <1>;
> > + phy-handle = <&phy5>;
> > + };
> > +
> > + mdio-bus {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + phy5: ethernet-phy at 5 {
> > + reg = <5>;
> > + phy-mode = "rgmii-rxid";
> > + };
> > + };
> > +};
> > +
> > &pio {
> > + gmac1_pins: eth at 0 {
> > + pins_eth {
> > + pinmux = <MT2701_PIN_275_MDC__FUNC_MDC>,
> > + <MT2701_PIN_276_MDIO__FUNC_MDIO>,
> > + <MT2701_PIN_262_G2_TXEN__FUNC_G2_TXEN>,
> > + <MT2701_PIN_263_G2_TXD3__FUNC_G2_TXD3>,
> > + <MT2701_PIN_264_G2_TXD2__FUNC_G2_TXD2>,
> > + <MT2701_PIN_265_G2_TXD1__FUNC_G2_TXD1>,
> > + <MT2701_PIN_266_G2_TXD0__FUNC_G2_TXD0>,
> > + <MT2701_PIN_267_G2_TXC__FUNC_G2_TXC>,
> > + <MT2701_PIN_268_G2_RXC__FUNC_G2_RXC>,
> > + <MT2701_PIN_269_G2_RXD0__FUNC_G2_RXD0>,
> > + <MT2701_PIN_270_G2_RXD1__FUNC_G2_RXD1>,
> > + <MT2701_PIN_271_G2_RXD2__FUNC_G2_RXD2>,
> > + <MT2701_PIN_272_G2_RXD3__FUNC_G2_RXD3>,
> > + <MT2701_PIN_274_G2_RXDV__FUNC_G2_RXDV>;
> > + };
> > + };
> > +
> > spi_pins_a: spi0 at 0 {
> > pins_spi {
> > pinmux = <MT2701_PIN_53_SPI0_CSN__FUNC_SPI0_CS>,
> > diff --git a/arch/arm/boot/dts/mt2701.dtsi b/arch/arm/boot/dts/mt2701.dtsi
> > index 4f52019..3847f70 100644
> > --- a/arch/arm/boot/dts/mt2701.dtsi
> > +++ b/arch/arm/boot/dts/mt2701.dtsi
> > @@ -381,6 +381,28 @@
> > #clock-cells = <1>;
> > };
> >
> > + eth: ethernet at 1b100000 {
> > + compatible = "mediatek,mt7623-eth";
> > + reg = <0 0x1b100000 0 0x20000>;
> > + interrupts = <GIC_SPI 200 IRQ_TYPE_LEVEL_LOW>,
> > + <GIC_SPI 199 IRQ_TYPE_LEVEL_LOW>,
> > + <GIC_SPI 198 IRQ_TYPE_LEVEL_LOW>;
> > + clocks = <&topckgen CLK_TOP_ETHIF_SEL>,
> > + <&apmixedsys CLK_APMIXED_TRGPLL>,
> > + <ðsys CLK_ETHSYS_ESW>,
> > + <ðsys CLK_ETHSYS_GP2>,
> > + <ðsys CLK_ETHSYS_GP1>;
> > + clock-names = "ethif", "trgpll", "esw", "gp2", "gp1";
> > + power-domains = <&scpsys MT2701_POWER_DOMAIN_ETH>;
> > + resets = <&watchdog MT2701_TOPRGU_ETHDMA_RST>;
>
> are you sure this is correct ? on mt7623 we point the reset at ethsys
> and not the watchdog.
>
> John
>
> > + reset-names = "eth";
> > + mediatek,ethsys = <ðsys>;
> > + mediatek,pctl = <&syscfg_pctl_a>;
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + status = "disabled";
> > + };
> > +
> > bdpsys: syscon at 1c000000 {
> > compatible = "mediatek,mt2701-bdpsys", "syscon";
> > reg = <0 0x1c000000 0 0x1000>;
> >
^ permalink raw reply
* [BISECT] ARM build errors on GCC v6.2 (crypto: arm/aes - replace scalar AES cipher)
From: Ard Biesheuvel @ 2017-01-14 15:21 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114142435.ashdj2xm5b3kzg2p@kozik-lap>
On 14 January 2017 at 14:24, Krzysztof Kozlowski <krzk@kernel.org> wrote:
> Hi,
>
> allyesconfig and multi_v7_defconfig fail to build on recent linux-next
> on GCC 6.2.0.
>
> Errors:
> ../arch/arm/crypto/aes-cipher-core.S: Assembler messages:
> ../arch/arm/crypto/aes-cipher-core.S:21: Error: selected processor does not support `tt .req ip' in ARM mode
> ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movw tt,#:lower16:crypto_ft_tab'
> ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movt tt,#:upper16:crypto_ft_tab'
>
> Compiler: arm-linux-gnueabi-gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005
>
> Git bisect pointed to:
> first bad commit: [81edb42629758bacdf813dd5e4542ae26e3ad73a] crypto: arm/aes - replace scalar AES cipher
>
Fixed in
https://git.kernel.org/cgit/linux/kernel/git/herbert/cryptodev-2.6.git/commit/?id=658fa754cd07
which should turn up in the next -next
^ permalink raw reply
* [PATCH 3/4] iio: adc: add a driver for the SAR ADC found in Amlogic Meson SoCs
From: Jonathan Cameron @ 2017-01-14 14:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170111174334.24343-4-martin.blumenstingl@googlemail.com>
On 11/01/17 17:43, Martin Blumenstingl wrote:
> This adds support for the SAR (Successive Approximation Register) ADC
> on the Amlogic Meson SoCs.
>
> The code is based on the public S805 (Meson8b) and S905 (GXBB)
> datasheets, as well as by reading (various versions of) the vendor
> driver and by inspecting the registers on the vendor kernels of my
> testing-hardware.
>
> Currently the GXBB, GXL and GXM SoCs are supported. GXBB hardware has
> 10-bit ADC resolution, while GXL and GXM have 12-bit ADC resolution.
> The code was written to support older SoCs (Meson8 and Meson8b) as well,
> but due to lack of actual testing-hardware no of_device_id was added for
> these.
>
> Two "features" from the vendor driver are currently missing:
> - the vendor driver uses channel #7 for calibration (this improves the
> accuracy of the results - in my tests the results were less than 3%
> off without calibration compared to the vendor driver). Adding support
> for this should be easy, but is not required for most applications.
> - channel #6 is connected to the SoCs internal temperature sensor.
> Adding support for this is probably not so easy since (based on the
> u-boot sources) most SoC versions are using different registers and
> algorithms for the conversion from "ADC value" to temperature.
>
> Supported by the hardware but currently not supported by the driver:
> - reading multiple channels at the same time (the hardware has a FIFO
> buffer which stores multiple results)
> - continuous sampling (this would require a way to enable this
> individually because otherwise the ADC would be drawing power
> constantly)
> - interrupt support (similar to the vendor driver this new driver is
> polling the results. It is unclear if the IRQ-mode is supported on
> older (Meson6 or Meson8) hardware as well or if there are any errata)
>
Russell Cc'd for a quick question on the clk api.
Ideally include a source for datasheets if available. Saves time googling and
perhaps getting the wrong thing!
A few other minor comments inline. Pretty good V1.
Jonathan
> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> ---
> drivers/iio/adc/Kconfig | 12 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/meson_saradc.c | 860 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 873 insertions(+)
> create mode 100644 drivers/iio/adc/meson_saradc.c
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 9c8b558ba19e..86059b9b91bf 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -371,6 +371,18 @@ config MEN_Z188_ADC
> This driver can also be built as a module. If so, the module will be
> called men_z188_adc.
>
> +config MESON_SARADC
> + tristate "Amlogic Meson SAR ADC driver"
> + default ARCH_MESON
> + depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST)
> + select REGMAP_MMIO
> + help
> + Say yes here to build support for the SAR ADC found in Amlogic Meson
> + SoCs.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called meson_saradc.
> +
> config MXS_LRADC
> tristate "Freescale i.MX23/i.MX28 LRADC"
> depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index d36c4be8d1fc..de05b9e75f8f 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -36,6 +36,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
> obj-$(CONFIG_MCP3422) += mcp3422.o
> obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o
> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
> +obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
> obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o
> obj-$(CONFIG_NAU7802) += nau7802.o
> obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
> new file mode 100644
> index 000000000000..06e8ac620385
> --- /dev/null
> +++ b/drivers/iio/adc/meson_saradc.c
> @@ -0,0 +1,860 @@
> +/*
> + * Amlogic Meson Successive Approximation Register (SAR) A/D Converter
> + *
> + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk-provider.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iio/iio.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/reset.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +
> +#define SAR_ADC_REG0 0x00
> + #define SAR_ADC_REG0_PANEL_DETECT BIT(31)
> + #define SAR_ADC_REG0_BUSY_MASK GENMASK(30, 28)
> + #define SAR_ADC_REG0_DELTA_BUSY BIT(30)
> + #define SAR_ADC_REG0_AVG_BUSY BIT(29)
> + #define SAR_ADC_REG0_SAMPLE_BUSY BIT(28)
> + #define SAR_ADC_REG0_FIFO_FULL BIT(27)
> + #define SAR_ADC_REG0_FIFO_EMPTY BIT(26)
> + #define SAR_ADC_REG0_FIFO_COUNT_MASK GENMASK(25, 21)
> + #define SAR_ADC_REG0_ADC_BIAS_CTRL_MASK GENMASK(20, 19)
> + #define SAR_ADC_REG0_CURR_CHAN_ID_MASK GENMASK(18, 16)
> + #define SAR_ADC_REG0_ADC_TEMP_SEN_SEL BIT(15)
> + #define SAR_ADC_REG0_SAMPLING_STOP BIT(14)
> + #define SAR_ADC_REG0_CHAN_DELTA_EN_MASK GENMASK(13, 12)
> + #define SAR_ADC_REG0_DETECT_IRQ_POL BIT(10)
> + #define SAR_ADC_REG0_DETECT_IRQ_EN BIT(9)
> + #define SAR_ADC_REG0_FIFO_CNT_IRQ_MASK GENMASK(8, 4)
> + #define SAR_ADC_REG0_FIFO_IRQ_EN BIT(3)
> + #define SAR_ADC_REG0_SAMPLING_START BIT(2)
> + #define SAR_ADC_REG0_CONTINUOUS_EN BIT(1)
> + #define SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE BIT(0)
> +
> +#define SAR_ADC_CHAN_LIST 0x04
> + #define SAR_ADC_CHAN_LIST_MAX_INDEX_MASK GENMASK(26, 24)
> + #define SAR_ADC_CHAN_CHAN_ENTRY_MASK(_chan) \
> + (GENMASK(2, 0) << (_chan * 3))
> +
> +#define SAR_ADC_AVG_CNTL 0x08
> + #define SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(_chan) \
> + (16 + (_chan * 2))
> + #define SAR_ADC_AVG_CNTL_AVG_MODE_MASK(_chan) \
> + (GENMASK(17, 16) << (_chan * 2))
> + #define SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(_chan) \
> + (0 + (_chan * 2))
> + #define SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(_chan) \
> + (GENMASK(1, 0) << (_chan * 2))
> +
> +#define SAR_ADC_REG3 0x0c
> + #define SAR_ADC_REG3_CNTL_USE_SC_DLY BIT(31)
> + #define SAR_ADC_REG3_CLK_EN BIT(30)
> + #define SAR_ADC_REG3_BL30_INITIALIZED BIT(28)
> + #define SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN BIT(27)
> + #define SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE BIT(26)
> + #define SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK GENMASK(25, 23)
> + #define SAR_ADC_REG3_DETECT_EN BIT(22)
> + #define SAR_ADC_REG3_ADC_EN BIT(21)
> + #define SAR_ADC_REG3_PANEL_DETECT_COUNT_MASK GENMASK(20, 18)
> + #define SAR_ADC_REG3_PANEL_DETECT_FILTER_TB_MASK GENMASK(17, 16)
> + #define SAR_ADC_REG3_ADC_CLK_DIV_SHIFT 10
> + #define SAR_ADC_REG3_ADC_CLK_DIV_WIDTH 5
> + #define SAR_ADC_REG3_ADC_CLK_DIV_MASK GENMASK(15, 10)
> + #define SAR_ADC_REG3_BLOCK_DLY_SEL_MASK GENMASK(9, 8)
> + #define SAR_ADC_REG3_BLOCK_DLY_MASK GENMASK(7, 0)
> +
> +#define SAR_ADC_DELAY 0x10
> + #define SAR_ADC_DELAY_INPUT_DLY_SEL_MASK GENMASK(25, 24)
> + #define SAR_ADC_DELAY_BL30_BUSY BIT(15)
> + #define SAR_ADC_DELAY_KERNEL_BUSY BIT(14)
> + #define SAR_ADC_DELAY_INPUT_DLY_CNT_MASK GENMASK(23, 16)
> + #define SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK GENMASK(9, 8)
> + #define SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK GENMASK(7, 0)
> +
> +#define SAR_ADC_LAST_RD 0x14
> + #define SAR_ADC_LAST_RD_LAST_CHANNEL1_MASK GENMASK(23, 16)
> + #define SAR_ADC_LAST_RD_LAST_CHANNEL0_MASK GENMASK(9, 0)
> +
> +#define SAR_ADC_FIFO_RD 0x18
> + #define SAR_ADC_FIFO_RD_CHAN_ID_MASK GENMASK(14, 12)
> + #define SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK GENMASK(11, 0)
> +
> +#define SAR_ADC_AUX_SW 0x1c
> + #define SAR_ADC_AUX_SW_MUX_SEL_CHAN_MASK(_chan) \
> + (GENMASK(10, 8) << ((_chan - 2) * 2))
> + #define SAR_ADC_AUX_SW_VREF_P_MUX BIT(6)
> + #define SAR_ADC_AUX_SW_VREF_N_MUX BIT(5)
> + #define SAR_ADC_AUX_SW_MODE_SEL BIT(4)
> + #define SAR_ADC_AUX_SW_YP_DRIVE_SW BIT(3)
> + #define SAR_ADC_AUX_SW_XP_DRIVE_SW BIT(2)
> + #define SAR_ADC_AUX_SW_YM_DRIVE_SW BIT(1)
> + #define SAR_ADC_AUX_SW_XM_DRIVE_SW BIT(0)
> +
> +#define SAR_ADC_CHAN_10_SW 0x20
> + #define SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK GENMASK(25, 23)
> + #define SAR_ADC_CHAN_10_SW_CHAN1_VREF_P_MUX BIT(22)
> + #define SAR_ADC_CHAN_10_SW_CHAN1_VREF_N_MUX BIT(21)
> + #define SAR_ADC_CHAN_10_SW_CHAN1_MODE_SEL BIT(20)
> + #define SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW BIT(19)
> + #define SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW BIT(18)
> + #define SAR_ADC_CHAN_10_SW_CHAN1_YM_DRIVE_SW BIT(17)
> + #define SAR_ADC_CHAN_10_SW_CHAN1_XM_DRIVE_SW BIT(16)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK GENMASK(9, 7)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_VREF_P_MUX BIT(6)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_VREF_N_MUX BIT(5)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_MODE_SEL BIT(4)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW BIT(3)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW BIT(2)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_YM_DRIVE_SW BIT(1)
> + #define SAR_ADC_CHAN_10_SW_CHAN0_XM_DRIVE_SW BIT(0)
> +
> +#define SAR_ADC_DETECT_IDLE_SW 0x24
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_SW_EN BIT(26)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK GENMASK(25, 23)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_P_MUX BIT(22)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_N_MUX BIT(21)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_SEL BIT(20)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YP_DRIVE_SW BIT(19)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XP_DRIVE_SW BIT(18)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YM_DRIVE_SW BIT(17)
> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XM_DRIVE_SW BIT(16)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK GENMASK(9, 7)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_P_MUX BIT(6)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_N_MUX BIT(5)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_SEL BIT(4)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YP_DRIVE_SW BIT(3)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XP_DRIVE_SW BIT(2)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YM_DRIVE_SW BIT(1)
> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XM_DRIVE_SW BIT(0)
> +
> +#define SAR_ADC_DELTA_10 0x28
> + #define SAR_ADC_DELTA_10_TEMP_SEL BIT(27)
> + #define SAR_ADC_DELTA_10_TS_REVE1 BIT(26)
> + #define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_SHIFT 16
> + #define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_MASK GENMASK(25, 16)
> + #define SAR_ADC_DELTA_10_TS_REVE0 BIT(15)
> + #define SAR_ADC_DELTA_10_TS_C_SHIFT 11
> + #define SAR_ADC_DELTA_10_TS_C_MASK GENMASK(14, 11)
> + #define SAR_ADC_DELTA_10_TS_VBG_EN BIT(10)
> + #define SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_SHIFT 0
> + #define SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_MASK GENMASK(9, 0)
> +
> +/* NOTE: registers from here are undocumented (the vendor Linux kernel driver
> + * and u-boot source served as reference). These only seem to be relevant on
> + * GXBB and newer.
> + */
> +#define SAR_ADC_REG11 0x2c
> + #define SAR_ADC_REG11_BANDGAP_EN BIT(13)
> +
> +#define SAR_ADC_REG13 0x34
> + #define SAR_ADC_REG13_12BIT_CALIBRATION_MASK GENMASK(13, 8)
> +
> +#define SAR_ADC_MAX_FIFO_SIZE 32
> +#define SAR_ADC_NUM_CHANNELS ARRAY_SIZE(meson_saradc_iio_channels)
> +#define SAR_ADC_VALUE_MASK(_priv) (BIT(_priv->resolution) - 1)
> +
> +#define MESON_SAR_ADC_CHAN(_chan, _type) { \
> + .type = _type, \
> + .indexed = true, \
> + .channel = _chan, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> + .datasheet_name = "SAR_ADC_CH"#_chan, \
> +}
> +
> +/* TODO: the hardware supports IIO_TEMP for channel 6 as well which is
Multline comment syntax.
> + * currently not supported by this driver.
> + */
> +static const struct iio_chan_spec meson_saradc_iio_channels[] = {
> + MESON_SAR_ADC_CHAN(0, IIO_VOLTAGE),
> + MESON_SAR_ADC_CHAN(1, IIO_VOLTAGE),
> + MESON_SAR_ADC_CHAN(2, IIO_VOLTAGE),
> + MESON_SAR_ADC_CHAN(3, IIO_VOLTAGE),
> + MESON_SAR_ADC_CHAN(4, IIO_VOLTAGE),
> + MESON_SAR_ADC_CHAN(5, IIO_VOLTAGE),
> + MESON_SAR_ADC_CHAN(6, IIO_VOLTAGE),
> + MESON_SAR_ADC_CHAN(7, IIO_VOLTAGE),
> + IIO_CHAN_SOFT_TIMESTAMP(8),
> +};
> +
> +enum meson_saradc_avg_mode {
> + NO_AVERAGING = 0x0,
> + MEAN_AVERAGING = 0x1,
> + MEDIAN_AVERAGING = 0x2,
> +};
> +
> +enum meson_saradc_num_samples {
> + ONE_SAMPLE = 0x0,
> + TWO_SAMPLES = 0x1,
> + FOUR_SAMPLES = 0x2,
> + EIGHT_SAMPLES = 0x3,
> +};
> +
> +enum meson_saradc_chan7_mux_sel {
> + CHAN7_MUX_VSS = 0x0,
> + CHAN7_MUX_VDD_DIV4 = 0x1,
> + CHAN7_MUX_VDD_DIV2 = 0x2,
> + CHAN7_MUX_VDD_MUL3_DIV4 = 0x3,
> + CHAN7_MUX_VDD = 0x4,
> + CHAN7_MUX_CH7_INPUT = 0x7,
> +};
> +
> +struct meson_saradc_priv {
> + struct regmap *regmap;
> + struct clk *clkin;
> + struct clk *core_clk;
> + struct clk *sana_clk;
> + struct clk *adc_sel_clk;
> + struct clk *adc_clk;
> + struct clk_gate clk_gate;
> + struct clk *adc_div_clk;
> + struct clk_divider clk_div;
> + struct regulator *vref;
> + struct completion completion;
> + u8 resolution;
> +};
> +
> +static const struct regmap_config meson_saradc_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 32,
> + .reg_stride = 4,
> + .max_register = SAR_ADC_REG13,
> +};
> +
> +static unsigned int meson_saradc_get_fifo_count(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + u32 regval;
> +
> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
> +
> + return FIELD_GET(SAR_ADC_REG0_FIFO_COUNT_MASK, regval);
> +}
> +
> +static int meson_saradc_wait_busy_clear(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + int regval, timeout = 10000;
> +
> + do {
> + udelay(1);
> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
> + } while (FIELD_GET(SAR_ADC_REG0_BUSY_MASK, regval) && timeout--);
> +
> + if (timeout < 0)
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
> +
> +static int meson_saradc_read_raw_sample(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int *val)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + int ret, regval, fifo_chan, fifo_val, sum = 0, count = 0;
> +
> + ret = meson_saradc_wait_busy_clear(indio_dev);
> + if (ret)
> + return ret;
> +
> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
> +
> + while (meson_saradc_get_fifo_count(indio_dev) > 0 &&
> + count < SAR_ADC_MAX_FIFO_SIZE) {
> + regmap_read(priv->regmap, SAR_ADC_FIFO_RD, ®val);
> +
> + fifo_chan = FIELD_GET(SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval);
> + if (fifo_chan == chan->channel) {
> + fifo_val = FIELD_GET(SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK,
> + regval) & SAR_ADC_VALUE_MASK(priv);
> + sum += fifo_val;
> + count++;
> + }
> + }
> +
> + if (!count)
> + return -ENOENT;
> +
> + *val = sum / count;
> +
> + return 0;
> +}
> +
> +static void meson_saradc_set_averaging(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum meson_saradc_avg_mode mode,
> + enum meson_saradc_num_samples samples)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + u32 val;
> +
> + val = samples << SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(chan->channel);
> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
> + SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(chan->channel),
> + val);
> +
> + val = mode << SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(chan->channel);
> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
> + SAR_ADC_AVG_CNTL_AVG_MODE_MASK(chan->channel), val);
> +}
> +
> +static void meson_saradc_enable_channel(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + u32 regval;
> +
> + /* the SAR ADC engine allows sampling multiple channels at the same
> + * time. to keep it simple we're only working with one *internal*
> + * channel, which starts counting at index 0 (which means: count = 1).
> + */
> + regval = FIELD_PREP(SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0);
> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
> + SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval);
> +
> + /* map channel index 0 to the channel which we want to read */
> + regval = FIELD_PREP(SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), chan->channel);
> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
> + SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), regval);
> +
> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
> + chan->channel);
> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
> + SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
> + regval);
> +
> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
> + chan->channel);
> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
> + SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
> + regval);
> +
> + if (chan->channel == 6)
> + regmap_update_bits(priv->regmap, SAR_ADC_DELTA_10,
> + SAR_ADC_DELTA_10_TEMP_SEL, 0);
> +}
> +
> +static void meson_saradc_set_channel7_mux(struct iio_dev *indio_dev,
> + enum meson_saradc_chan7_mux_sel sel)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + u32 regval;
> +
> + regval = FIELD_PREP(SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel);
> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
> + SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval);
> +
> + usleep_range(10, 20);
> +}
> +
> +static void meson_saradc_start_sample_engine(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> +
> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE,
> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE);
> +
> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
> + SAR_ADC_REG0_SAMPLING_START,
> + SAR_ADC_REG0_SAMPLING_START);
> +}
> +
> +static void meson_saradc_stop_sample_engine(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> +
> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
> + SAR_ADC_REG0_SAMPLING_STOP,
> + SAR_ADC_REG0_SAMPLING_STOP);
> +
> + /* wait until all modules are stopped */
> + meson_saradc_wait_busy_clear(indio_dev);
> +
> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, 0);
> +}
> +
> +static void meson_saradc_lock(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + int val;
> +
> + mutex_lock(&indio_dev->mlock);
> +
> + /* prevent BL30 from using the SAR ADC while we are using it */
> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
> + SAR_ADC_DELAY_KERNEL_BUSY,
> + SAR_ADC_DELAY_KERNEL_BUSY);
> +
> + /* wait until BL30 releases it's lock (so we can use the SAR ADC) */
> + do {
> + udelay(1);
> + regmap_read(priv->regmap, SAR_ADC_DELAY, &val);
> + } while (val & SAR_ADC_DELAY_BL30_BUSY);
> +}
> +
> +static void meson_saradc_unlock(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> +
> + /* allow BL30 to use the SAR ADC again */
> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
> + SAR_ADC_DELAY_KERNEL_BUSY, 0);
> +
> + mutex_unlock(&indio_dev->mlock);
> +}
> +
> +static int meson_saradc_get_sample(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum meson_saradc_avg_mode avg_mode,
> + enum meson_saradc_num_samples avg_samples,
> + int *val)
> +{
> + int ret, tmp;
> +
> + meson_saradc_lock(indio_dev);
> +
> + /* clear old values from the FIFO buffer, ignoring errors */
> + meson_saradc_read_raw_sample(indio_dev, chan, &tmp);
> +
> + meson_saradc_set_averaging(indio_dev, chan, avg_mode, avg_samples);
> +
> + meson_saradc_enable_channel(indio_dev, chan);
> +
> + meson_saradc_start_sample_engine(indio_dev);
> + ret = meson_saradc_read_raw_sample(indio_dev, chan, val);
> + meson_saradc_stop_sample_engine(indio_dev);
> +
> + meson_saradc_unlock(indio_dev);
> +
> + if (ret) {
> + dev_warn(&indio_dev->dev,
> + "failed to read sample for channel %d: %d\n",
> + chan->channel, ret);
> + return ret;
> + }
> +
> + return IIO_VAL_INT;
> +}
> +
> +static int meson_saradc_iio_info_read_raw(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int *val, int *val2, long mask)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + return meson_saradc_get_sample(indio_dev, chan, NO_AVERAGING,
> + ONE_SAMPLE, val);
> + break;
> +
> + case IIO_CHAN_INFO_AVERAGE_RAW:
> + return meson_saradc_get_sample(indio_dev, chan, MEAN_AVERAGING,
> + EIGHT_SAMPLES, val);
> + break;
> +
> + case IIO_CHAN_INFO_SCALE:
> + ret = regulator_get_voltage(priv->vref);
> + if (ret < 0) {
> + dev_err(&indio_dev->dev,
> + "failed to get vref voltage: %d\n", ret);
> + return ret;
> + }
> +
> + *val = ret / 1000;
> + *val2 = priv->resolution;
> + return IIO_VAL_FRACTIONAL_LOG2;
> +
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int meson_saradc_clk_init(struct iio_dev *indio_dev, void __iomem *base)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + struct clk_init_data init;
> + char clk_name[32];
> + const char *clk_parents[1];
> +
> + snprintf(clk_name, sizeof(clk_name), "%s#adc_div",
> + of_node_full_name(indio_dev->dev.of_node));
> + init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
> + init.flags = 0;
> + init.ops = &clk_divider_ops;
> + clk_parents[0] = __clk_get_name(priv->clkin);
> + init.parent_names = clk_parents;
> + init.num_parents = 1;
> +
> + priv->clk_div.reg = base + SAR_ADC_REG3;
> + priv->clk_div.shift = SAR_ADC_REG3_ADC_CLK_DIV_SHIFT;
> + priv->clk_div.width = SAR_ADC_REG3_ADC_CLK_DIV_WIDTH;
> + priv->clk_div.hw.init = &init;
> + priv->clk_div.flags = 0;
> +
> + priv->adc_div_clk = devm_clk_register(&indio_dev->dev,
> + &priv->clk_div.hw);
> + if (WARN_ON(IS_ERR(priv->adc_div_clk)))
> + return PTR_ERR(priv->adc_div_clk);
> +
> + snprintf(clk_name, sizeof(clk_name), "%s#adc_en",
> + of_node_full_name(indio_dev->dev.of_node));
> + init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
> + init.flags = CLK_SET_RATE_PARENT;
> + init.ops = &clk_gate_ops;
> + clk_parents[0] = __clk_get_name(priv->adc_div_clk);
> + init.parent_names = clk_parents;
> + init.num_parents = 1;
> +
> + priv->clk_gate.reg = base + SAR_ADC_REG3;
> + priv->clk_gate.bit_idx = fls(SAR_ADC_REG3_CLK_EN);
> + priv->clk_gate.hw.init = &init;
> +
> + priv->adc_clk = devm_clk_register(&indio_dev->dev, &priv->clk_gate.hw);
> + if (WARN_ON(IS_ERR(priv->adc_clk)))
> + return PTR_ERR(priv->adc_clk);
> +
> + return 0;
> +}
> +
> +static int meson_saradc_init(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + int regval, ret;
> +
> + /* make sure we start at CH7 input */
why? Seems like a little more detail would be good here ;)
> + meson_saradc_set_channel7_mux(indio_dev, CHAN7_MUX_CH7_INPUT);
> +
> + regmap_read(priv->regmap, SAR_ADC_REG3, ®val);
> + if (regval & SAR_ADC_REG3_BL30_INITIALIZED) {
> + dev_info(&indio_dev->dev, "already initialized by BL30\n");
> + return 0;
> + }
> +
> + dev_info(&indio_dev->dev, "initializing SAR ADC\n");
I'd argue this provides no useful info so should be dropped.
Useful for debugging no doubt, but just noise going forward.
> +
> + meson_saradc_stop_sample_engine(indio_dev);
> +
> + /* update the channel 6 MUX to select the temperature sensor */
> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
> + SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
> + SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
> +
> + /* disable all channels by default */
> + regmap_write(priv->regmap, SAR_ADC_CHAN_LIST, 0x0);
> +
> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
> + SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE, 0);
> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
> + SAR_ADC_REG3_CNTL_USE_SC_DLY,
> + SAR_ADC_REG3_CNTL_USE_SC_DLY);
> +
> + /* delay between two samples = (10+1) * 1uS */
> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
> + SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
> + FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK, 10));
> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
> + SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
> + FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, 0));
> +
> + /* delay between two samples = (10+1) * 1uS */
> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
> + SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
> + FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, 10));
> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
> + SAR_ADC_DELAY_INPUT_DLY_SEL_MASK,
> + FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, 1));
> +
Cool. I hadn't come across FIELD_PREP before. Neater and tidier than having
a shift and a mask for at least some usecases.
> + ret = clk_set_parent(priv->adc_sel_clk, priv->clkin);
> + if (ret) {
> + dev_err(&indio_dev->dev,
> + "failed to set adc parent to clkin\n");
> + return ret;
> + }
> +
> + ret = clk_set_rate(priv->adc_clk, 1200000);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to set adc clock rate\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int meson_saradc_hw_enable(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> + int ret;
> +
> + meson_saradc_lock(indio_dev);
> +
> + ret = regulator_enable(priv->vref);
> + if (ret < 0) {
> + dev_err(&indio_dev->dev, "failed to enable vref regulator\n");
> + goto err_vref;
> + }
> +
> + ret = clk_prepare_enable(priv->core_clk);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to enable core clk\n");
> + goto err_core_clk;
> + }
> +
> + ret = clk_prepare_enable(priv->sana_clk);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to enable sana clk\n");
> + goto err_sana_clk;
> + }
> +
> + regmap_update_bits(priv->regmap, SAR_ADC_REG11,
> + SAR_ADC_REG11_BANDGAP_EN, SAR_ADC_REG11_BANDGAP_EN);
Is this controlling an offset for a bandgap or some actual electronics?
Not sure if it should be disabled on error and the datasheets I've found are
far from great! You disable it in the disable, so I'd expect it to be
unwound on error in here too.
> + regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN,
> + SAR_ADC_REG3_ADC_EN);
The fact you turn this of in the disable suggests to me that on error
you should be doing it in here too.
> +
> + udelay(5);
> +
> + ret = clk_prepare_enable(priv->adc_clk);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to enable adc_en clk\n");
> + goto err_adc_clk;
> + }
> +
> + meson_saradc_unlock(indio_dev);
> +
> + return 0;
> +
> +err_adc_clk:
> + clk_disable_unprepare(priv->sana_clk);
> +err_sana_clk:
> + clk_disable_unprepare(priv->core_clk);
> +err_core_clk:
> + regulator_disable(priv->vref);
> +err_vref:
> + meson_saradc_unlock(indio_dev);
> + return ret;
> +}
> +
> +static void meson_saradc_hw_disable(struct iio_dev *indio_dev)
> +{
> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
> +
> + meson_saradc_lock(indio_dev);
> +
> + clk_disable_unprepare(priv->adc_clk);
> +
> + regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN, 0);
> + regmap_update_bits(priv->regmap, SAR_ADC_REG11,
> + SAR_ADC_REG11_BANDGAP_EN, 0);
> +
> + clk_disable_unprepare(priv->sana_clk);
> + clk_disable_unprepare(priv->core_clk);
> +
> + regulator_disable(priv->vref);
> +
> + meson_saradc_unlock(indio_dev);
> +}
> +
> +static const struct iio_info meson_saradc_iio_info = {
> + .read_raw = meson_saradc_iio_info_read_raw,
> + .driver_module = THIS_MODULE,
> +};
> +
> +static const struct of_device_id meson_saradc_of_match[] = {
> + {
> + .compatible = "amlogic,meson-gxbb-saradc",
> + .data = (void *)10,
Might have been worth having a structure array indexed from an enum.
For now it is overkill, but seems likely there are a few other differences
that aren't supported yet?
> + }, {
> + .compatible = "amlogic,meson-gxl-saradc",
> + .data = (void *)12,
> + },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, meson_saradc_of_match);
> +
> +static int meson_saradc_probe(struct platform_device *pdev)
> +{
> + struct meson_saradc_priv *priv;
> + struct iio_dev *indio_dev;
> + struct resource *res;
> + void __iomem *base;
> + const struct of_device_id *match;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
> + if (!indio_dev) {
> + dev_err(&pdev->dev, "failed allocating iio device\n");
> + return -ENOMEM;
> + }
> +
> + priv = iio_priv(indio_dev);
> +
> + match = of_match_device(meson_saradc_of_match, &pdev->dev);
> + priv->resolution = (unsigned long)match->data;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
> + &meson_saradc_regmap_config);
> + if (IS_ERR(priv->regmap))
> + return PTR_ERR(priv->regmap);
> +
> + init_completion(&priv->completion);
> +
> + priv->clkin = devm_clk_get(&pdev->dev, "clkin");
> + if (IS_ERR(priv->clkin)) {
> + dev_err(&pdev->dev, "failed to get clkin\n");
> + return PTR_ERR(priv->clkin);
> + }
> +
> + priv->core_clk = devm_clk_get(&pdev->dev, "core");
> + if (IS_ERR(priv->core_clk)) {
> + dev_err(&pdev->dev, "failed to get core clk\n");
> + return PTR_ERR(priv->core_clk);
> + }
> +
> + priv->sana_clk = devm_clk_get(&pdev->dev, "sana");
Oh for a devm_clk_get_optional to handle this boiler plate neatly.
It's been suggested before, but nothing seems to have come of it.
Some array clk get functions might also clean this up a touch.
> + if (IS_ERR(priv->sana_clk)) {
> + if (PTR_ERR(priv->sana_clk) == -ENOENT) {
> + priv->sana_clk = NULL;
> + } else {
> + dev_err(&pdev->dev, "failed to get sana clk\n");
> + return PTR_ERR(priv->sana_clk);
> + }
> + }
> +
> + priv->adc_clk = devm_clk_get(&pdev->dev, "adc_clk");
> + if (IS_ERR(priv->adc_clk)) {
> + if (PTR_ERR(priv->adc_clk) == -ENOENT) {
> + priv->adc_clk = NULL;
> + } else {
> + dev_err(&pdev->dev, "failed to get adc clk\n");
> + return PTR_ERR(priv->adc_clk);
> + }
> + }
> +
> + priv->adc_sel_clk = devm_clk_get(&pdev->dev, "adc_sel");
> + if (IS_ERR(priv->adc_sel_clk)) {
> + if (PTR_ERR(priv->adc_sel_clk) == -ENOENT) {
> + priv->adc_sel_clk = NULL;
> + } else {
> + dev_err(&pdev->dev, "failed to get adc_sel clk\n");
> + return PTR_ERR(priv->adc_sel_clk);
> + }
> + }
> +
> + /* on pre-GXBB SoCs the SAR ADC itself provides the ADC clock: */
> + if (!priv->adc_clk) {
> + ret = meson_saradc_clk_init(indio_dev, base);
> + if (ret)
> + return ret;
> + }
> +
> + priv->vref = devm_regulator_get(&pdev->dev, "vref");
> + if (IS_ERR(priv->vref)) {
> + dev_err(&pdev->dev, "failed to get vref regulator\n");
> + return PTR_ERR(priv->vref);
> + }
> +
> + ret = meson_saradc_init(indio_dev);
> + if (ret)
> + goto err;
> +
> + ret = meson_saradc_hw_enable(indio_dev);
> + if (ret)
> + goto err;
> +
> + platform_set_drvdata(pdev, indio_dev);
> +
> + indio_dev->name = dev_name(&pdev->dev);
> + indio_dev->dev.parent = &pdev->dev;
> + indio_dev->dev.of_node = pdev->dev.of_node;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->info = &meson_saradc_iio_info;
> +
> + indio_dev->channels = meson_saradc_iio_channels;
> + indio_dev->num_channels = SAR_ADC_NUM_CHANNELS;
> +
> + ret = iio_device_register(indio_dev);
> + if (ret)
> + goto err_hw;
> +
> + return 0;
> +
> +err_hw:
> + meson_saradc_hw_disable(indio_dev);
> +err:
> + return ret;
> +}
> +
> +static int meson_saradc_remove(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +
> + meson_saradc_hw_disable(indio_dev);
> + iio_device_unregister(indio_dev);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int meson_saradc_suspend(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +
> + meson_saradc_hw_disable(indio_dev);
> +
> + return 0;
> +}
> +
> +static int meson_saradc_resume(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +
> + return meson_saradc_hw_enable(indio_dev);
> +}
> +#endif /* CONFIG_PM_SLEEP */
> +
> +static SIMPLE_DEV_PM_OPS(meson_saradc_pm_ops,
> + meson_saradc_suspend, meson_saradc_resume);
> +
> +static struct platform_driver meson_saradc_driver = {
> + .probe = meson_saradc_probe,
> + .remove = meson_saradc_remove,
> + .driver = {
> + .name = "meson-saradc",
> + .of_match_table = meson_saradc_of_match,
> + .pm = &meson_saradc_pm_ops,
> + },
> +};
> +
> +module_platform_driver(meson_saradc_driver);
> +
> +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
> +MODULE_DESCRIPTION("Amlogic Meson SAR ADC driver");
> +MODULE_LICENSE("GPL v2");
>
^ permalink raw reply
* [BISECT] ARM build errors on GCC v6.2 (crypto: arm/aes - replace scalar AES cipher)
From: Krzysztof Kozlowski @ 2017-01-14 14:24 UTC (permalink / raw)
To: linux-arm-kernel
Hi,
allyesconfig and multi_v7_defconfig fail to build on recent linux-next
on GCC 6.2.0.
Errors:
../arch/arm/crypto/aes-cipher-core.S: Assembler messages:
../arch/arm/crypto/aes-cipher-core.S:21: Error: selected processor does not support `tt .req ip' in ARM mode
../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movw tt,#:lower16:crypto_ft_tab'
../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movt tt,#:upper16:crypto_ft_tab'
Compiler: arm-linux-gnueabi-gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005
Git bisect pointed to:
first bad commit: [81edb42629758bacdf813dd5e4542ae26e3ad73a] crypto: arm/aes - replace scalar AES cipher
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH 3/4] iio: adc: add a driver for the SAR ADC found in Amlogic Meson SoCs
From: Jonathan Cameron @ 2017-01-14 14:06 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <3e32fc4b-d28c-c003-d292-39cad5179839@kernel.org>
On 14/01/17 13:57, Jonathan Cameron wrote:
> On 13/01/17 23:48, Martin Blumenstingl wrote:
>> Hi Heiner,
>>
>> On Fri, Jan 13, 2017 at 9:26 PM, Heiner Kallweit <hkallweit1@gmail.com> wrote:
>> [snip]
>>>>>> +static int meson_saradc_read_raw_sample(struct iio_dev *indio_dev,
>>>>>> + const struct iio_chan_spec *chan,
>>>>>> + int *val)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> + int ret, regval, fifo_chan, fifo_val, sum = 0, count = 0;
>>>>>> +
>>>>>> + ret = meson_saradc_wait_busy_clear(indio_dev);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
>>>>> The resulting regval value isn't used, therefore this statement doesn't seem
>>>>> to be needed.
>>>> I can probably replace this with "0", good catch!
>>>>
>>>>> In the vendor driver there is such a dummy statement before reading the busy
>>>>> flags in REG0 after starting sampling. Reason seems to be a potential race
>>>>> when we try to read the busy flags before the sampling engine has set them.
>>>>> This isn't needed in meson_saradc_wait_busy_clear here as an udelay(1) is
>>>>> done first always.
>>>> do you think it's worth adding a comment here that a do ... while loop
>>>> is there on purpose?
>>>>
>>> Yes, a hint would be good that there's a potential race.
>>> Else there's a good chance that a future refactoring introduces a regression.
>> indeed, a comment will be part of v2
>>
>>>>>> +
>>>>>> + while (meson_saradc_get_fifo_count(indio_dev) > 0 &&
>>>>> IMHO this loop isn't needed. When we come here the FIFO contains exactly
>>>>> one element. This is true also in averaging mode as the averaging engine
>>>>> writes only the resulting mean value to the FIFO.
>>>>>
>>>>> And we can't have multiple samples active in parallel due to the locking
>>>>> done in meson_saradc_get_sample.
>>>>>
>>>>> By the way: I use an IRQ here to wake up when the FIFO contains one
>>>>> element. But as you wrote: It's not clear whether this works on all
>>>>> Meson systems.
>>>> maybe I should switch to IRQ mode as even the old Meson6 vendor kernel
>>>> sources indicate that the SAR ADC has IRQ support?
>>>>
>>> As you like. We can also add interrupt mode later (but leave polling intact)
>>> and activate it only if an interrupt is set in DT.
>>> This way we'd have a fallback in case there should be a problem with
>>> interrupt mode on some system.
>> I'm fine with either way, my idea was to keep it simple for the start.
>> as a side-note: even if we get rid of polling mode we still need
>> something like meson_saradc_wait_busy_clear(), because it's required
>> to stop sampling (at least in some cases) according to the datasheet:
>> "To stop sampling, simply set This bit and wait for all processing
>> modules to no longer indicate that they are busy."
>>
>>>>>> + count < SAR_ADC_MAX_FIFO_SIZE) {
>>>>>> + regmap_read(priv->regmap, SAR_ADC_FIFO_RD, ®val);
>>>>>> +
>>>>>> + fifo_chan = FIELD_GET(SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval);
>>>>>> + if (fifo_chan == chan->channel) {
>>>>>> + fifo_val = FIELD_GET(SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK,
>>>>>> + regval) & SAR_ADC_VALUE_MASK(priv);
>>>>>> + sum += fifo_val;
>>>>>> + count++;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + if (!count)
>>>>>> + return -ENOENT;
>>>>>> +
>>>>>> + *val = sum / count;
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void meson_saradc_set_averaging(struct iio_dev *indio_dev,
>>>>>> + const struct iio_chan_spec *chan,
>>>>>> + enum meson_saradc_avg_mode mode,
>>>>>> + enum meson_saradc_num_samples samples)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> + u32 val;
>>>>>> +
>>>>>> + val = samples << SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(chan->channel);
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
>>>>>> + SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(chan->channel),
>>>>>> + val);
>>>>>> +
>>>>>> + val = mode << SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(chan->channel);
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
>>>>>> + SAR_ADC_AVG_CNTL_AVG_MODE_MASK(chan->channel), val);
>>>>>> +}
>>>>>> +
>>>>>> +static void meson_saradc_enable_channel(struct iio_dev *indio_dev,
>>>>>> + const struct iio_chan_spec *chan)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> + u32 regval;
>>>>>> +
>>>>>> + /* the SAR ADC engine allows sampling multiple channels at the same
>>>>>> + * time. to keep it simple we're only working with one *internal*
>>>>>> + * channel, which starts counting at index 0 (which means: count = 1).
>>>>>> + */
>>>>>> + regval = FIELD_PREP(SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0);
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
>>>>>> + SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval);
>>>>>> +
>>>>>> + /* map channel index 0 to the channel which we want to read */
>>>>>> + regval = FIELD_PREP(SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), chan->channel);
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
>>>>>> + SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), regval);
>>>>>> +
>>>>>> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
>>>>>> + chan->channel);
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
>>>>>> + SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
>>>>>> + regval);
>>>>>> +
>>>>>> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
>>>>>> + chan->channel);
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
>>>>>> + SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
>>>>>> + regval);
>>>>>> +
>>>>>> + if (chan->channel == 6)
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_DELTA_10,
>>>>>> + SAR_ADC_DELTA_10_TEMP_SEL, 0);
>>>>>> +}
>>>>>> +
>>>>>> +static void meson_saradc_set_channel7_mux(struct iio_dev *indio_dev,
>>>>>> + enum meson_saradc_chan7_mux_sel sel)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> + u32 regval;
>>>>>> +
>>>>>> + regval = FIELD_PREP(SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel);
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
>>>>>> + SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval);
>>>>>> +
>>>>>> + usleep_range(10, 20);
>>>>>> +}
>>>>>> +
>>>>>> +static void meson_saradc_start_sample_engine(struct iio_dev *indio_dev)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> +
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>>>>>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE,
>>>>>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE);
>>>>>> +
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>>>>>> + SAR_ADC_REG0_SAMPLING_START,
>>>>>> + SAR_ADC_REG0_SAMPLING_START);
>>>>>> +}
>>>>>> +
>>>>>> +static void meson_saradc_stop_sample_engine(struct iio_dev *indio_dev)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> +
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>>>>>> + SAR_ADC_REG0_SAMPLING_STOP,
>>>>>> + SAR_ADC_REG0_SAMPLING_STOP);
>>>>>> +
>>>>>> + /* wait until all modules are stopped */
>>>>>> + meson_saradc_wait_busy_clear(indio_dev);
>>>>>> +
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>>>>>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, 0);
>>>>>> +}
>>>>>> +
>>>>>> +static void meson_saradc_lock(struct iio_dev *indio_dev)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> + int val;
>>>>>> +
>>>>>> + mutex_lock(&indio_dev->mlock);
>>>>>> +
>>>>>> + /* prevent BL30 from using the SAR ADC while we are using it */
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>>>>>> + SAR_ADC_DELAY_KERNEL_BUSY,
>>>>>> + SAR_ADC_DELAY_KERNEL_BUSY);
>>>>>> +
>>>>>> + /* wait until BL30 releases it's lock (so we can use the SAR ADC) */
>>>>>> + do {
>>>>>> + udelay(1);
>>>>>> + regmap_read(priv->regmap, SAR_ADC_DELAY, &val);
>>>>>> + } while (val & SAR_ADC_DELAY_BL30_BUSY);
>>>>>> +}
>>>>>> +
>>>>>> +static void meson_saradc_unlock(struct iio_dev *indio_dev)
>>>>>> +{
>>>>>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>>>>>> +
>>>>>> + /* allow BL30 to use the SAR ADC again */
>>>>>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>>>>>> + SAR_ADC_DELAY_KERNEL_BUSY, 0);
>>>>>> +
>>>>>> + mutex_unlock(&indio_dev->mlock);
>>>>>> +}
>>>>>> +
>>>>>> +static int meson_saradc_get_sample(struct iio_dev *indio_dev,
>>>>>> + const struct iio_chan_spec *chan,
>>>>>> + enum meson_saradc_avg_mode avg_mode,
>>>>>> + enum meson_saradc_num_samples avg_samples,
>>>>>> + int *val)
>>>>>> +{
>>>>>> + int ret, tmp;
>>>>>> +
>>>>>> + meson_saradc_lock(indio_dev);
>>>>>> +
>>>>>> + /* clear old values from the FIFO buffer, ignoring errors */
>>>>>> + meson_saradc_read_raw_sample(indio_dev, chan, &tmp);
>>>>>> +
>>>>>> + meson_saradc_set_averaging(indio_dev, chan, avg_mode, avg_samples);
>>>>>> +
>>>>>> + meson_saradc_enable_channel(indio_dev, chan);
>>>>>> +
>>>>>> + meson_saradc_start_sample_engine(indio_dev);
>>>>>> + ret = meson_saradc_read_raw_sample(indio_dev, chan, val);
>>>>>> + meson_saradc_stop_sample_engine(indio_dev);
>>>>>> +
>>>>>> + meson_saradc_unlock(indio_dev);
>>>>>> +
>>>>>> + if (ret) {
>>>>>> + dev_warn(&indio_dev->dev,
>>>>> Using the struct device in indio_dev results in IMHO ugly messages like
>>>>> iio iio:device0: already initialized by BL30
>>>>>
>>>>> We should use the parent instead, this is more readable:
>>>>> meson-saradc c1108680.adc: already initialized by BL30
>>>>>
>>>>> For this we need to move the assignment to indio_dev->dev.parent
>>>>> in probe, else messages may be written when parent isn't set yet.
>>>> indeed, I'll change this - thanks for the hint!
>> a little correction on this: I am all for setting the parent early.
>> However, I think that devm_iio_device_alloc() itself should take care
>> of building the device name correctly (to prevent code-duplication
>> across drivers in drivers/iio/*).
>> as an example: phy_create() does this (which includes the OF node
>> name): dev_set_name(&phy->dev, "phy-%s.%d", dev_name(dev), id);
>> iio_device_alloc does it like this (which is obviously missing the OF
>> node name): dev_set_name(&dev->dev, "iio:device%d", dev->id);
>>
> Propose a patch if you would like to.
> People 'shouldn't' be relying on this naming so hopefully we'll get
> away with such an user space interface change without anyone noticing!
>
Err, hang on. I didn't think this through. This same name is used
for the naming of the chardev. Changing it is a nonstarter I'm
afraid unless there is some way of splitting the two uses.
Jonathan
> Jonathan
>>
>> Regards,
>> Martin
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
>> the body of a message to majordomo at vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox