Commit 423b40e1 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'hsi_fixes_for_3.4' of git://gitorious.org/kernel-hsi/kernel-hsi

Pull HSI fixes and ABI documentation from Carlos Chinea

* tag 'hsi_fixes_for_3.4' of git://gitorious.org/kernel-hsi/kernel-hsi:
  HSI: Add HSI ABI documentation
  HSI: hsi_char: Remove max_data_size from sysfs
  HSI: hsi: Rework hsi_event interface
  HSI: hsi: Remove controllers and ports from the bus
  HSI: hsi: Fix error path cleanup on client registration
  HSI: hsi: Rework hsi_controller release
parents 95f71472 24b7099a
What: /sys/bus/hsi
Date: April 2012
KernelVersion: 3.4
Contact: Carlos Chinea <carlos.chinea@nokia.com>
Description:
High Speed Synchronous Serial Interface (HSI) is a
serial interface mainly used for connecting application
engines (APE) with cellular modem engines (CMT) in cellular
handsets.
The bus will be populated with devices (hsi_clients) representing
the protocols available in the system. Bus drivers implement
those protocols.
What: /sys/bus/hsi/devices/.../modalias
Date: April 2012
KernelVersion: 3.4
Contact: Carlos Chinea <carlos.chinea@nokia.com>
Description: Stores the same MODALIAS value emitted by uevent
Format: hsi:<hsi_client device name>
...@@ -123,7 +123,7 @@ struct hsc_client_data { ...@@ -123,7 +123,7 @@ struct hsc_client_data {
static unsigned int hsc_major; static unsigned int hsc_major;
/* Maximum buffer size that hsi_char will accept from userspace */ /* Maximum buffer size that hsi_char will accept from userspace */
static unsigned int max_data_size = 0x1000; static unsigned int max_data_size = 0x1000;
module_param(max_data_size, uint, S_IRUSR | S_IWUSR); module_param(max_data_size, uint, 0);
MODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)"); MODULE_PARM_DESC(max_data_size, "max read/write data size [4,8..65536] (^2)");
static void hsc_add_tail(struct hsc_channel *channel, struct hsi_msg *msg, static void hsc_add_tail(struct hsc_channel *channel, struct hsi_msg *msg,
......
...@@ -21,26 +21,13 @@ ...@@ -21,26 +21,13 @@
*/ */
#include <linux/hsi/hsi.h> #include <linux/hsi/hsi.h>
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/rwsem.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/kobject.h> #include <linux/kobject.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/notifier.h>
#include "hsi_core.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, static ssize_t modalias_show(struct device *dev,
struct device_attribute *a __maybe_unused, char *buf) struct device_attribute *a __maybe_unused, char *buf)
{ {
...@@ -54,7 +41,6 @@ static struct device_attribute hsi_bus_dev_attrs[] = { ...@@ -54,7 +41,6 @@ static struct device_attribute hsi_bus_dev_attrs[] = {
static int hsi_bus_uevent(struct device *dev, struct kobj_uevent_env *env) 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; return 0;
...@@ -80,12 +66,10 @@ static void hsi_client_release(struct device *dev) ...@@ -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) static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info)
{ {
struct hsi_client *cl; struct hsi_client *cl;
unsigned long flags;
cl = kzalloc(sizeof(*cl), GFP_KERNEL); cl = kzalloc(sizeof(*cl), GFP_KERNEL);
if (!cl) if (!cl)
return; return;
cl->device.type = &hsi_cl;
cl->tx_cfg = info->tx_cfg; cl->tx_cfg = info->tx_cfg;
cl->rx_cfg = info->rx_cfg; cl->rx_cfg = info->rx_cfg;
cl->device.bus = &hsi_bus_type; cl->device.bus = &hsi_bus_type;
...@@ -93,14 +77,11 @@ static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info) ...@@ -93,14 +77,11 @@ static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info)
cl->device.release = hsi_client_release; cl->device.release = hsi_client_release;
dev_set_name(&cl->device, info->name); dev_set_name(&cl->device, info->name);
cl->device.platform_data = info->platform_data; 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) if (info->archdata)
cl->device.archdata = *info->archdata; cl->device.archdata = *info->archdata;
if (device_register(&cl->device) < 0) { if (device_register(&cl->device) < 0) {
pr_err("hsi: failed to register client: %s\n", info->name); 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) ...@@ -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) 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); device_unregister(dev);
return 0; return 0;
...@@ -140,12 +114,17 @@ static int hsi_remove_port(struct device *dev, void *data __maybe_unused) ...@@ -140,12 +114,17 @@ static int hsi_remove_port(struct device *dev, void *data __maybe_unused)
return 0; 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) ...@@ -170,20 +149,12 @@ int hsi_register_controller(struct hsi_controller *hsi)
unsigned int i; unsigned int i;
int err; int err;
hsi->device.type = &hsi_ctrl; err = device_add(&hsi->device);
hsi->device.bus = &hsi_bus_type;
hsi->device.release = hsi_controller_release;
err = device_register(&hsi->device);
if (err < 0) if (err < 0)
return err; return err;
for (i = 0; i < hsi->num_ports; i++) { for (i = 0; i < hsi->num_ports; i++) {
hsi->port[i].device.parent = &hsi->device; hsi->port[i]->device.parent = &hsi->device;
hsi->port[i].device.bus = &hsi_bus_type; err = device_add(&hsi->port[i]->device);
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);
if (err < 0) if (err < 0)
goto out; goto out;
} }
...@@ -192,7 +163,9 @@ int hsi_register_controller(struct hsi_controller *hsi) ...@@ -192,7 +163,9 @@ int hsi_register_controller(struct hsi_controller *hsi)
return 0; return 0;
out: out:
hsi_unregister_controller(hsi); while (i-- > 0)
device_del(&hsi->port[i]->device);
device_del(&hsi->device);
return err; return err;
} }
...@@ -222,6 +195,29 @@ static inline int hsi_dummy_cl(struct hsi_client *cl __maybe_unused) ...@@ -222,6 +195,29 @@ static inline int hsi_dummy_cl(struct hsi_client *cl __maybe_unused)
return 0; 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 * hsi_alloc_controller - Allocate an HSI controller and its ports
* @n_ports: Number of ports on the HSI controller * @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) ...@@ -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_alloc_controller(unsigned int n_ports, gfp_t flags)
{ {
struct hsi_controller *hsi; struct hsi_controller *hsi;
struct hsi_port *port; struct hsi_port **port;
unsigned int i; unsigned int i;
if (!n_ports) if (!n_ports)
return NULL; return NULL;
port = kzalloc(sizeof(*port)*n_ports, flags);
if (!port)
return NULL;
hsi = kzalloc(sizeof(*hsi), flags); hsi = kzalloc(sizeof(*hsi), flags);
if (!hsi) if (!hsi)
goto out; return NULL;
for (i = 0; i < n_ports; i++) { port = kzalloc(sizeof(*port)*n_ports, flags);
dev_set_name(&port[i].device, "port%d", i); if (!port) {
port[i].num = i; kfree(hsi);
port[i].async = hsi_dummy_msg; return NULL;
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);
} }
hsi->num_ports = n_ports; hsi->num_ports = n_ports;
hsi->port = port; 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; return hsi;
out: out:
kfree(port); hsi_put_controller(hsi);
return NULL; return NULL;
} }
EXPORT_SYMBOL_GPL(hsi_alloc_controller); 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 * hsi_free_msg - Free an HSI message
* @msg: Pointer to the HSI message * @msg: Pointer to the HSI message
...@@ -414,37 +407,67 @@ void hsi_release_port(struct hsi_client *cl) ...@@ -414,37 +407,67 @@ void hsi_release_port(struct hsi_client *cl)
} }
EXPORT_SYMBOL_GPL(hsi_release_port); 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) struct hsi_client *cl = container_of(nb, struct hsi_client, nb);
(*cl->hsi_start_rx)(cl);
(*cl->ehandler)(cl, event);
return 0; 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) struct hsi_port *port = hsi_get_port(cl);
(*cl->hsi_stop_rx)(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); WARN_ON(!hsi_port_claimed(cl));
list_for_each_entry(cl, &port->clients, link) {
spin_unlock(&port->clock);
(*fn)(cl, data);
spin_lock(&port->clock);
}
spin_unlock(&port->clock);
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 * hsi_event -Notifies clients about port events
...@@ -458,22 +481,12 @@ static int hsi_port_for_each_client(struct hsi_port *port, void *data, ...@@ -458,22 +481,12 @@ static int hsi_port_for_each_client(struct hsi_port *port, void *data,
* Events: * Events:
* HSI_EVENT_START_RX - Incoming wake line high * HSI_EVENT_START_RX - Incoming wake line high
* HSI_EVENT_STOP_RX - Incoming wake line down * 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); return atomic_notifier_call_chain(&port->n_head, event, NULL);
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);
} }
EXPORT_SYMBOL_GPL(hsi_event); EXPORT_SYMBOL_GPL(hsi_event);
......
...@@ -26,9 +26,9 @@ ...@@ -26,9 +26,9 @@
#include <linux/device.h> #include <linux/device.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/scatterlist.h> #include <linux/scatterlist.h>
#include <linux/spinlock.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/notifier.h>
/* HSI message ttype */ /* HSI message ttype */
#define HSI_MSG_READ 0 #define HSI_MSG_READ 0
...@@ -121,18 +121,18 @@ static inline int hsi_register_board_info(struct hsi_board_info const *info, ...@@ -121,18 +121,18 @@ static inline int hsi_register_board_info(struct hsi_board_info const *info,
* @device: Driver model representation of the device * @device: Driver model representation of the device
* @tx_cfg: HSI TX configuration * @tx_cfg: HSI TX configuration
* @rx_cfg: HSI RX configuration * @rx_cfg: HSI RX configuration
* @hsi_start_rx: Called after incoming wake line goes high * @e_handler: Callback for handling port events (RX Wake High/Low)
* @hsi_stop_rx: Called after incoming wake line goes low * @pclaimed: Keeps tracks if the clients claimed its associated HSI port
* @nb: Notifier block for port events
*/ */
struct hsi_client { struct hsi_client {
struct device device; struct device device;
struct hsi_config tx_cfg; struct hsi_config tx_cfg;
struct hsi_config rx_cfg; struct hsi_config rx_cfg;
void (*hsi_start_rx)(struct hsi_client *cl);
void (*hsi_stop_rx)(struct hsi_client *cl);
/* private: */ /* private: */
void (*ehandler)(struct hsi_client *, unsigned long);
unsigned int pclaimed:1; unsigned int pclaimed:1;
struct list_head link; struct notifier_block nb;
}; };
#define to_hsi_client(dev) container_of(dev, struct hsi_client, device) #define to_hsi_client(dev) container_of(dev, struct hsi_client, device)
...@@ -147,6 +147,10 @@ static inline void *hsi_client_drvdata(struct hsi_client *cl) ...@@ -147,6 +147,10 @@ static inline void *hsi_client_drvdata(struct hsi_client *cl)
return dev_get_drvdata(&cl->device); return dev_get_drvdata(&cl->device);
} }
int hsi_register_port_event(struct hsi_client *cl,
void (*handler)(struct hsi_client *, unsigned long));
int hsi_unregister_port_event(struct hsi_client *cl);
/** /**
* struct hsi_client_driver - Driver associated to an HSI client * struct hsi_client_driver - Driver associated to an HSI client
* @driver: Driver model representation of the driver * @driver: Driver model representation of the driver
...@@ -214,8 +218,7 @@ void hsi_free_msg(struct hsi_msg *msg); ...@@ -214,8 +218,7 @@ void hsi_free_msg(struct hsi_msg *msg);
* @start_tx: Callback to inform that a client wants to TX data * @start_tx: Callback to inform that a client wants to TX data
* @stop_tx: Callback to inform that a client no longer wishes to TX data * @stop_tx: Callback to inform that a client no longer wishes to TX data
* @release: Callback to inform that a client no longer uses the port * @release: Callback to inform that a client no longer uses the port
* @clients: List of hsi_clients using the port. * @n_head: Notifier chain for signaling port events to the clients.
* @clock: Lock to serialize access to the clients list.
*/ */
struct hsi_port { struct hsi_port {
struct device device; struct device device;
...@@ -231,14 +234,14 @@ struct hsi_port { ...@@ -231,14 +234,14 @@ struct hsi_port {
int (*start_tx)(struct hsi_client *cl); int (*start_tx)(struct hsi_client *cl);
int (*stop_tx)(struct hsi_client *cl); int (*stop_tx)(struct hsi_client *cl);
int (*release)(struct hsi_client *cl); int (*release)(struct hsi_client *cl);
struct list_head clients; /* private */
spinlock_t clock; struct atomic_notifier_head n_head;
}; };
#define to_hsi_port(dev) container_of(dev, struct hsi_port, device) #define to_hsi_port(dev) container_of(dev, struct hsi_port, device)
#define hsi_get_port(cl) to_hsi_port((cl)->device.parent) #define hsi_get_port(cl) to_hsi_port((cl)->device.parent)
void hsi_event(struct hsi_port *port, unsigned int event); int hsi_event(struct hsi_port *port, unsigned long event);
int hsi_claim_port(struct hsi_client *cl, unsigned int share); int hsi_claim_port(struct hsi_client *cl, unsigned int share);
void hsi_release_port(struct hsi_client *cl); void hsi_release_port(struct hsi_client *cl);
...@@ -270,13 +273,13 @@ struct hsi_controller { ...@@ -270,13 +273,13 @@ struct hsi_controller {
struct module *owner; struct module *owner;
unsigned int id; unsigned int id;
unsigned int num_ports; unsigned int num_ports;
struct hsi_port *port; struct hsi_port **port;
}; };
#define to_hsi_controller(dev) container_of(dev, struct hsi_controller, device) #define to_hsi_controller(dev) container_of(dev, struct hsi_controller, device)
struct hsi_controller *hsi_alloc_controller(unsigned int n_ports, gfp_t flags); struct hsi_controller *hsi_alloc_controller(unsigned int n_ports, gfp_t flags);
void hsi_free_controller(struct hsi_controller *hsi); void hsi_put_controller(struct hsi_controller *hsi);
int hsi_register_controller(struct hsi_controller *hsi); int hsi_register_controller(struct hsi_controller *hsi);
void hsi_unregister_controller(struct hsi_controller *hsi); void hsi_unregister_controller(struct hsi_controller *hsi);
...@@ -294,7 +297,7 @@ static inline void *hsi_controller_drvdata(struct hsi_controller *hsi) ...@@ -294,7 +297,7 @@ static inline void *hsi_controller_drvdata(struct hsi_controller *hsi)
static inline struct hsi_port *hsi_find_port_num(struct hsi_controller *hsi, static inline struct hsi_port *hsi_find_port_num(struct hsi_controller *hsi,
unsigned int num) unsigned int num)
{ {
return (num < hsi->num_ports) ? &hsi->port[num] : NULL; return (num < hsi->num_ports) ? hsi->port[num] : NULL;
} }
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment