mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-07 22:03:14 +00:00
HSI: Add common DT binding for HSI client devices
Implement and document generic DT bindings for HSI clients. Signed-off-by: Sebastian Reichel <sre@kernel.org> Reviewed-by: Pavel Machek <pavel@ucw.cz> Tested-By: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
This commit is contained in:
parent
8491451024
commit
a2aa24734d
44
Documentation/devicetree/bindings/hsi/client-devices.txt
Normal file
44
Documentation/devicetree/bindings/hsi/client-devices.txt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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/slab.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <linux/notifier.h>
|
#include <linux/notifier.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
#include "hsi_core.h"
|
#include "hsi_core.h"
|
||||||
|
|
||||||
static ssize_t modalias_show(struct device *dev,
|
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)
|
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 = {
|
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)
|
int hsi_remove_client(struct device *dev, void *data __maybe_unused)
|
||||||
{
|
{
|
||||||
device_unregister(dev);
|
device_unregister(dev);
|
||||||
@ -505,7 +709,7 @@ int hsi_unregister_port_event(struct hsi_client *cl)
|
|||||||
EXPORT_SYMBOL_GPL(hsi_unregister_port_event);
|
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
|
* @port: Port where the event occurred
|
||||||
* @event: The event type
|
* @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);
|
int hsi_remove_client(struct device *dev, void *data);
|
||||||
void hsi_port_unregister_clients(struct hsi_port *port);
|
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,
|
static inline void hsi_controller_set_drvdata(struct hsi_controller *hsi,
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user