|
@@ -21,26 +21,13 @@
|
|
|
*/
|
|
|
#include <linux/hsi/hsi.h>
|
|
|
#include <linux/compiler.h>
|
|
|
-#include <linux/rwsem.h>
|
|
|
#include <linux/list.h>
|
|
|
-#include <linux/spinlock.h>
|
|
|
#include <linux/kobject.h>
|
|
|
#include <linux/slab.h>
|
|
|
#include <linux/string.h>
|
|
|
+#include <linux/notifier.h>
|
|
|
#include "hsi_core.h"
|
|
|
|
|
|
-static struct device_type hsi_ctrl = {
|
|
|
- .name = "hsi_controller",
|
|
|
-};
|
|
|
-
|
|
|
-static struct device_type hsi_cl = {
|
|
|
- .name = "hsi_client",
|
|
|
-};
|
|
|
-
|
|
|
-static struct device_type hsi_port = {
|
|
|
- .name = "hsi_port",
|
|
|
-};
|
|
|
-
|
|
|
static ssize_t modalias_show(struct device *dev,
|
|
|
struct device_attribute *a __maybe_unused, char *buf)
|
|
|
{
|
|
@@ -54,8 +41,7 @@ static struct device_attribute hsi_bus_dev_attrs[] = {
|
|
|
|
|
|
static int hsi_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
|
{
|
|
|
- if (dev->type == &hsi_cl)
|
|
|
- add_uevent_var(env, "MODALIAS=hsi:%s", dev_name(dev));
|
|
|
+ add_uevent_var(env, "MODALIAS=hsi:%s", dev_name(dev));
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -80,12 +66,10 @@ static void hsi_client_release(struct device *dev)
|
|
|
static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info)
|
|
|
{
|
|
|
struct hsi_client *cl;
|
|
|
- unsigned long flags;
|
|
|
|
|
|
cl = kzalloc(sizeof(*cl), GFP_KERNEL);
|
|
|
if (!cl)
|
|
|
return;
|
|
|
- cl->device.type = &hsi_cl;
|
|
|
cl->tx_cfg = info->tx_cfg;
|
|
|
cl->rx_cfg = info->rx_cfg;
|
|
|
cl->device.bus = &hsi_bus_type;
|
|
@@ -93,14 +77,11 @@ static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info)
|
|
|
cl->device.release = hsi_client_release;
|
|
|
dev_set_name(&cl->device, info->name);
|
|
|
cl->device.platform_data = info->platform_data;
|
|
|
- spin_lock_irqsave(&port->clock, flags);
|
|
|
- list_add_tail(&cl->link, &port->clients);
|
|
|
- spin_unlock_irqrestore(&port->clock, flags);
|
|
|
if (info->archdata)
|
|
|
cl->device.archdata = *info->archdata;
|
|
|
if (device_register(&cl->device) < 0) {
|
|
|
pr_err("hsi: failed to register client: %s\n", info->name);
|
|
|
- kfree(cl);
|
|
|
+ put_device(&cl->device);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -120,13 +101,6 @@ static void hsi_scan_board_info(struct hsi_controller *hsi)
|
|
|
|
|
|
static int hsi_remove_client(struct device *dev, void *data __maybe_unused)
|
|
|
{
|
|
|
- struct hsi_client *cl = to_hsi_client(dev);
|
|
|
- struct hsi_port *port = to_hsi_port(dev->parent);
|
|
|
- unsigned long flags;
|
|
|
-
|
|
|
- spin_lock_irqsave(&port->clock, flags);
|
|
|
- list_del(&cl->link);
|
|
|
- spin_unlock_irqrestore(&port->clock, flags);
|
|
|
device_unregister(dev);
|
|
|
|
|
|
return 0;
|
|
@@ -140,12 +114,17 @@ static int hsi_remove_port(struct device *dev, void *data __maybe_unused)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static void hsi_controller_release(struct device *dev __maybe_unused)
|
|
|
+static void hsi_controller_release(struct device *dev)
|
|
|
{
|
|
|
+ struct hsi_controller *hsi = to_hsi_controller(dev);
|
|
|
+
|
|
|
+ kfree(hsi->port);
|
|
|
+ kfree(hsi);
|
|
|
}
|
|
|
|
|
|
-static void hsi_port_release(struct device *dev __maybe_unused)
|
|
|
+static void hsi_port_release(struct device *dev)
|
|
|
{
|
|
|
+ kfree(to_hsi_port(dev));
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -170,20 +149,12 @@ int hsi_register_controller(struct hsi_controller *hsi)
|
|
|
unsigned int i;
|
|
|
int err;
|
|
|
|
|
|
- hsi->device.type = &hsi_ctrl;
|
|
|
- hsi->device.bus = &hsi_bus_type;
|
|
|
- hsi->device.release = hsi_controller_release;
|
|
|
- err = device_register(&hsi->device);
|
|
|
+ err = device_add(&hsi->device);
|
|
|
if (err < 0)
|
|
|
return err;
|
|
|
for (i = 0; i < hsi->num_ports; i++) {
|
|
|
- hsi->port[i].device.parent = &hsi->device;
|
|
|
- hsi->port[i].device.bus = &hsi_bus_type;
|
|
|
- hsi->port[i].device.release = hsi_port_release;
|
|
|
- hsi->port[i].device.type = &hsi_port;
|
|
|
- INIT_LIST_HEAD(&hsi->port[i].clients);
|
|
|
- spin_lock_init(&hsi->port[i].clock);
|
|
|
- err = device_register(&hsi->port[i].device);
|
|
|
+ hsi->port[i]->device.parent = &hsi->device;
|
|
|
+ err = device_add(&hsi->port[i]->device);
|
|
|
if (err < 0)
|
|
|
goto out;
|
|
|
}
|
|
@@ -192,7 +163,9 @@ int hsi_register_controller(struct hsi_controller *hsi)
|
|
|
|
|
|
return 0;
|
|
|
out:
|
|
|
- hsi_unregister_controller(hsi);
|
|
|
+ while (i-- > 0)
|
|
|
+ device_del(&hsi->port[i]->device);
|
|
|
+ device_del(&hsi->device);
|
|
|
|
|
|
return err;
|
|
|
}
|
|
@@ -222,6 +195,29 @@ static inline int hsi_dummy_cl(struct hsi_client *cl __maybe_unused)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * hsi_put_controller - Free an HSI controller
|
|
|
+ *
|
|
|
+ * @hsi: Pointer to the HSI controller to freed
|
|
|
+ *
|
|
|
+ * HSI controller drivers should only use this function if they need
|
|
|
+ * to free their allocated hsi_controller structures before a successful
|
|
|
+ * call to hsi_register_controller. Other use is not allowed.
|
|
|
+ */
|
|
|
+void hsi_put_controller(struct hsi_controller *hsi)
|
|
|
+{
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ if (!hsi)
|
|
|
+ return;
|
|
|
+
|
|
|
+ for (i = 0; i < hsi->num_ports; i++)
|
|
|
+ if (hsi->port && hsi->port[i])
|
|
|
+ put_device(&hsi->port[i]->device);
|
|
|
+ put_device(&hsi->device);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(hsi_put_controller);
|
|
|
+
|
|
|
/**
|
|
|
* hsi_alloc_controller - Allocate an HSI controller and its ports
|
|
|
* @n_ports: Number of ports on the HSI controller
|
|
@@ -232,54 +228,51 @@ static inline int hsi_dummy_cl(struct hsi_client *cl __maybe_unused)
|
|
|
struct hsi_controller *hsi_alloc_controller(unsigned int n_ports, gfp_t flags)
|
|
|
{
|
|
|
struct hsi_controller *hsi;
|
|
|
- struct hsi_port *port;
|
|
|
+ struct hsi_port **port;
|
|
|
unsigned int i;
|
|
|
|
|
|
if (!n_ports)
|
|
|
return NULL;
|
|
|
|
|
|
- port = kzalloc(sizeof(*port)*n_ports, flags);
|
|
|
- if (!port)
|
|
|
- return NULL;
|
|
|
hsi = kzalloc(sizeof(*hsi), flags);
|
|
|
if (!hsi)
|
|
|
- goto out;
|
|
|
- for (i = 0; i < n_ports; i++) {
|
|
|
- dev_set_name(&port[i].device, "port%d", i);
|
|
|
- port[i].num = i;
|
|
|
- port[i].async = hsi_dummy_msg;
|
|
|
- port[i].setup = hsi_dummy_cl;
|
|
|
- port[i].flush = hsi_dummy_cl;
|
|
|
- port[i].start_tx = hsi_dummy_cl;
|
|
|
- port[i].stop_tx = hsi_dummy_cl;
|
|
|
- port[i].release = hsi_dummy_cl;
|
|
|
- mutex_init(&port[i].lock);
|
|
|
+ return NULL;
|
|
|
+ port = kzalloc(sizeof(*port)*n_ports, flags);
|
|
|
+ if (!port) {
|
|
|
+ kfree(hsi);
|
|
|
+ return NULL;
|
|
|
}
|
|
|
hsi->num_ports = n_ports;
|
|
|
hsi->port = port;
|
|
|
+ hsi->device.release = hsi_controller_release;
|
|
|
+ device_initialize(&hsi->device);
|
|
|
+
|
|
|
+ for (i = 0; i < n_ports; i++) {
|
|
|
+ port[i] = kzalloc(sizeof(**port), flags);
|
|
|
+ if (port[i] == NULL)
|
|
|
+ goto out;
|
|
|
+ port[i]->num = i;
|
|
|
+ port[i]->async = hsi_dummy_msg;
|
|
|
+ port[i]->setup = hsi_dummy_cl;
|
|
|
+ port[i]->flush = hsi_dummy_cl;
|
|
|
+ port[i]->start_tx = hsi_dummy_cl;
|
|
|
+ port[i]->stop_tx = hsi_dummy_cl;
|
|
|
+ port[i]->release = hsi_dummy_cl;
|
|
|
+ mutex_init(&port[i]->lock);
|
|
|
+ ATOMIC_INIT_NOTIFIER_HEAD(&port[i]->n_head);
|
|
|
+ dev_set_name(&port[i]->device, "port%d", i);
|
|
|
+ hsi->port[i]->device.release = hsi_port_release;
|
|
|
+ device_initialize(&hsi->port[i]->device);
|
|
|
+ }
|
|
|
|
|
|
return hsi;
|
|
|
out:
|
|
|
- kfree(port);
|
|
|
+ hsi_put_controller(hsi);
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(hsi_alloc_controller);
|
|
|
|
|
|
-/**
|
|
|
- * hsi_free_controller - Free an HSI controller
|
|
|
- * @hsi: Pointer to HSI controller
|
|
|
- */
|
|
|
-void hsi_free_controller(struct hsi_controller *hsi)
|
|
|
-{
|
|
|
- if (!hsi)
|
|
|
- return;
|
|
|
-
|
|
|
- kfree(hsi->port);
|
|
|
- kfree(hsi);
|
|
|
-}
|
|
|
-EXPORT_SYMBOL_GPL(hsi_free_controller);
|
|
|
-
|
|
|
/**
|
|
|
* hsi_free_msg - Free an HSI message
|
|
|
* @msg: Pointer to the HSI message
|
|
@@ -414,37 +407,67 @@ void hsi_release_port(struct hsi_client *cl)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(hsi_release_port);
|
|
|
|
|
|
-static int hsi_start_rx(struct hsi_client *cl, void *data __maybe_unused)
|
|
|
+static int hsi_event_notifier_call(struct notifier_block *nb,
|
|
|
+ unsigned long event, void *data __maybe_unused)
|
|
|
{
|
|
|
- if (cl->hsi_start_rx)
|
|
|
- (*cl->hsi_start_rx)(cl);
|
|
|
+ struct hsi_client *cl = container_of(nb, struct hsi_client, nb);
|
|
|
+
|
|
|
+ (*cl->ehandler)(cl, event);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int hsi_stop_rx(struct hsi_client *cl, void *data __maybe_unused)
|
|
|
+/**
|
|
|
+ * hsi_register_port_event - Register a client to receive port events
|
|
|
+ * @cl: HSI client that wants to receive port events
|
|
|
+ * @cb: Event handler callback
|
|
|
+ *
|
|
|
+ * Clients should register a callback to be able to receive
|
|
|
+ * events from the ports. Registration should happen after
|
|
|
+ * claiming the port.
|
|
|
+ * The handler can be called in interrupt context.
|
|
|
+ *
|
|
|
+ * Returns -errno on error, or 0 on success.
|
|
|
+ */
|
|
|
+int hsi_register_port_event(struct hsi_client *cl,
|
|
|
+ void (*handler)(struct hsi_client *, unsigned long))
|
|
|
{
|
|
|
- if (cl->hsi_stop_rx)
|
|
|
- (*cl->hsi_stop_rx)(cl);
|
|
|
+ struct hsi_port *port = hsi_get_port(cl);
|
|
|
|
|
|
- return 0;
|
|
|
+ if (!handler || cl->ehandler)
|
|
|
+ return -EINVAL;
|
|
|
+ if (!hsi_port_claimed(cl))
|
|
|
+ return -EACCES;
|
|
|
+ cl->ehandler = handler;
|
|
|
+ cl->nb.notifier_call = hsi_event_notifier_call;
|
|
|
+
|
|
|
+ return atomic_notifier_chain_register(&port->n_head, &cl->nb);
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(hsi_register_port_event);
|
|
|
|
|
|
-static int hsi_port_for_each_client(struct hsi_port *port, void *data,
|
|
|
- int (*fn)(struct hsi_client *cl, void *data))
|
|
|
+/**
|
|
|
+ * hsi_unregister_port_event - Stop receiving port events for a client
|
|
|
+ * @cl: HSI client that wants to stop receiving port events
|
|
|
+ *
|
|
|
+ * Clients should call this function before releasing their associated
|
|
|
+ * port.
|
|
|
+ *
|
|
|
+ * Returns -errno on error, or 0 on success.
|
|
|
+ */
|
|
|
+int hsi_unregister_port_event(struct hsi_client *cl)
|
|
|
{
|
|
|
- struct hsi_client *cl;
|
|
|
+ struct hsi_port *port = hsi_get_port(cl);
|
|
|
+ int err;
|
|
|
|
|
|
- spin_lock(&port->clock);
|
|
|
- list_for_each_entry(cl, &port->clients, link) {
|
|
|
- spin_unlock(&port->clock);
|
|
|
- (*fn)(cl, data);
|
|
|
- spin_lock(&port->clock);
|
|
|
- }
|
|
|
- spin_unlock(&port->clock);
|
|
|
+ WARN_ON(!hsi_port_claimed(cl));
|
|
|
|
|
|
- return 0;
|
|
|
+ err = atomic_notifier_chain_unregister(&port->n_head, &cl->nb);
|
|
|
+ if (!err)
|
|
|
+ cl->ehandler = NULL;
|
|
|
+
|
|
|
+ return err;
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(hsi_unregister_port_event);
|
|
|
|
|
|
/**
|
|
|
* hsi_event -Notifies clients about port events
|
|
@@ -458,22 +481,12 @@ static int hsi_port_for_each_client(struct hsi_port *port, void *data,
|
|
|
* Events:
|
|
|
* HSI_EVENT_START_RX - Incoming wake line high
|
|
|
* HSI_EVENT_STOP_RX - Incoming wake line down
|
|
|
+ *
|
|
|
+ * Returns -errno on error, or 0 on success.
|
|
|
*/
|
|
|
-void hsi_event(struct hsi_port *port, unsigned int event)
|
|
|
+int hsi_event(struct hsi_port *port, unsigned long event)
|
|
|
{
|
|
|
- int (*fn)(struct hsi_client *cl, void *data);
|
|
|
-
|
|
|
- switch (event) {
|
|
|
- case HSI_EVENT_START_RX:
|
|
|
- fn = hsi_start_rx;
|
|
|
- break;
|
|
|
- case HSI_EVENT_STOP_RX:
|
|
|
- fn = hsi_stop_rx;
|
|
|
- break;
|
|
|
- default:
|
|
|
- return;
|
|
|
- }
|
|
|
- hsi_port_for_each_client(port, NULL, fn);
|
|
|
+ return atomic_notifier_call_chain(&port->n_head, event, NULL);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(hsi_event);
|
|
|
|