Skip to content
Snippets Groups Projects
Commit a2aa2473 authored by Sebastian Reichel's avatar Sebastian Reichel
Browse files

HSI: Add common DT binding for HSI client devices


Implement and document generic DT bindings for HSI clients.

Signed-off-by: default avatarSebastian Reichel <sre@kernel.org>
Reviewed-by: default avatarPavel Machek <pavel@ucw.cz>
Tested-By: default avatarIvaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
parent 84914510
No related branches found
No related tags found
No related merge requests found
Each HSI port is supposed to have one child node, which
symbols the remote device connected to the HSI port. The
following properties are standardized for HSI clients:
Required HSI configuration properties:
- hsi-channel-ids: A list of channel ids
- hsi-rx-mode: Receiver Bit transmission mode ("stream" or "frame")
- hsi-tx-mode: Transmitter Bit transmission mode ("stream" or "frame")
- hsi-mode: May be used instead hsi-rx-mode and hsi-tx-mode if
the transmission mode is the same for receiver and
transmitter
- hsi-speed-kbps: Max bit transmission speed in kbit/s
- hsi-flow: RX flow type ("synchronized" or "pipeline")
- hsi-arb-mode: Arbitration mode for TX frame ("round-robin", "priority")
Optional HSI configuration properties:
- hsi-channel-names: A list with one name per channel specified in the
hsi-channel-ids property
Device Tree node example for an HSI client:
hsi-controller {
hsi-port {
modem: hsi-client {
compatible = "nokia,n900-modem";
hsi-channel-ids = <0>, <1>, <2>, <3>;
hsi-channel-names = "mcsaab-control",
"speech-control",
"speech-data",
"mcsaab-data";
hsi-speed-kbps = <55000>;
hsi-mode = "frame";
hsi-flow = "synchronized";
hsi-arb-mode = "round-robin";
/* more client specific properties */
};
};
};
......@@ -26,6 +26,8 @@
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include "hsi_core.h"
static ssize_t modalias_show(struct device *dev,
......@@ -50,7 +52,13 @@ static int hsi_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
static int hsi_bus_match(struct device *dev, struct device_driver *driver)
{
return strcmp(dev_name(dev), driver->name) == 0;
if (of_driver_match_device(dev, driver))
return true;
if (strcmp(dev_name(dev), driver->name) == 0)
return true;
return false;
}
static struct bus_type hsi_bus_type = {
......@@ -123,6 +131,202 @@ static void hsi_scan_board_info(struct hsi_controller *hsi)
}
}
#ifdef CONFIG_OF
static struct hsi_board_info hsi_char_dev_info = {
.name = "hsi_char",
};
static int hsi_of_property_parse_mode(struct device_node *client, char *name,
unsigned int *result)
{
const char *mode;
int err;
err = of_property_read_string(client, name, &mode);
if (err < 0)
return err;
if (strcmp(mode, "stream") == 0)
*result = HSI_MODE_STREAM;
else if (strcmp(mode, "frame") == 0)
*result = HSI_MODE_FRAME;
else
return -EINVAL;
return 0;
}
static int hsi_of_property_parse_flow(struct device_node *client, char *name,
unsigned int *result)
{
const char *flow;
int err;
err = of_property_read_string(client, name, &flow);
if (err < 0)
return err;
if (strcmp(flow, "synchronized") == 0)
*result = HSI_FLOW_SYNC;
else if (strcmp(flow, "pipeline") == 0)
*result = HSI_FLOW_PIPE;
else
return -EINVAL;
return 0;
}
static int hsi_of_property_parse_arb_mode(struct device_node *client,
char *name, unsigned int *result)
{
const char *arb_mode;
int err;
err = of_property_read_string(client, name, &arb_mode);
if (err < 0)
return err;
if (strcmp(arb_mode, "round-robin") == 0)
*result = HSI_ARB_RR;
else if (strcmp(arb_mode, "priority") == 0)
*result = HSI_ARB_PRIO;
else
return -EINVAL;
return 0;
}
static void hsi_add_client_from_dt(struct hsi_port *port,
struct device_node *client)
{
struct hsi_client *cl;
struct hsi_channel channel;
struct property *prop;
char name[32];
int length, cells, err, i, max_chan, mode;
cl = kzalloc(sizeof(*cl), GFP_KERNEL);
if (!cl)
return;
err = of_modalias_node(client, name, sizeof(name));
if (err)
goto err;
dev_set_name(&cl->device, "%s", name);
err = hsi_of_property_parse_mode(client, "hsi-mode", &mode);
if (err) {
err = hsi_of_property_parse_mode(client, "hsi-rx-mode",
&cl->rx_cfg.mode);
if (err)
goto err;
err = hsi_of_property_parse_mode(client, "hsi-tx-mode",
&cl->tx_cfg.mode);
if (err)
goto err;
} else {
cl->rx_cfg.mode = mode;
cl->tx_cfg.mode = mode;
}
err = of_property_read_u32(client, "hsi-speed-kbps",
&cl->tx_cfg.speed);
if (err)
goto err;
cl->rx_cfg.speed = cl->tx_cfg.speed;
err = hsi_of_property_parse_flow(client, "hsi-flow",
&cl->rx_cfg.flow);
if (err)
goto err;
err = hsi_of_property_parse_arb_mode(client, "hsi-arb-mode",
&cl->rx_cfg.arb_mode);
if (err)
goto err;
prop = of_find_property(client, "hsi-channel-ids", &length);
if (!prop) {
err = -EINVAL;
goto err;
}
cells = length / sizeof(u32);
cl->rx_cfg.num_channels = cells;
cl->tx_cfg.num_channels = cells;
cl->rx_cfg.channels = kzalloc(cells * sizeof(channel), GFP_KERNEL);
if (!cl->rx_cfg.channels) {
err = -ENOMEM;
goto err;
}
cl->tx_cfg.channels = kzalloc(cells * sizeof(channel), GFP_KERNEL);
if (!cl->tx_cfg.channels) {
err = -ENOMEM;
goto err2;
}
max_chan = 0;
for (i = 0; i < cells; i++) {
err = of_property_read_u32_index(client, "hsi-channel-ids", i,
&channel.id);
if (err)
goto err3;
err = of_property_read_string_index(client, "hsi-channel-names",
i, &channel.name);
if (err)
channel.name = NULL;
if (channel.id > max_chan)
max_chan = channel.id;
cl->rx_cfg.channels[i] = channel;
cl->tx_cfg.channels[i] = channel;
}
cl->rx_cfg.num_hw_channels = max_chan + 1;
cl->tx_cfg.num_hw_channels = max_chan + 1;
cl->device.bus = &hsi_bus_type;
cl->device.parent = &port->device;
cl->device.release = hsi_client_release;
cl->device.of_node = client;
if (device_register(&cl->device) < 0) {
pr_err("hsi: failed to register client: %s\n", name);
put_device(&cl->device);
goto err3;
}
return;
err3:
kfree(cl->tx_cfg.channels);
err2:
kfree(cl->rx_cfg.channels);
err:
kfree(cl);
pr_err("hsi client: missing or incorrect of property: err=%d\n", err);
}
void hsi_add_clients_from_dt(struct hsi_port *port, struct device_node *clients)
{
struct device_node *child;
/* register hsi-char device */
hsi_new_client(port, &hsi_char_dev_info);
for_each_available_child_of_node(clients, child)
hsi_add_client_from_dt(port, child);
}
EXPORT_SYMBOL_GPL(hsi_add_clients_from_dt);
#endif
int hsi_remove_client(struct device *dev, void *data __maybe_unused)
{
device_unregister(dev);
......@@ -505,7 +709,7 @@ int hsi_unregister_port_event(struct hsi_client *cl)
EXPORT_SYMBOL_GPL(hsi_unregister_port_event);
/**
* hsi_event -Notifies clients about port events
* hsi_event - Notifies clients about port events
* @port: Port where the event occurred
* @event: The event type
*
......
......@@ -301,6 +301,17 @@ struct hsi_client *hsi_new_client(struct hsi_port *port,
int hsi_remove_client(struct device *dev, void *data);
void hsi_port_unregister_clients(struct hsi_port *port);
#ifdef CONFIG_OF
void hsi_add_clients_from_dt(struct hsi_port *port,
struct device_node *clients);
#else
static inline void hsi_add_clients_from_dt(struct hsi_port *port,
struct device_node *clients)
{
return;
}
#endif
static inline void hsi_controller_set_drvdata(struct hsi_controller *hsi,
void *data)
{
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment