Commit c9733c79 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'hsi-for-3.16' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-hsi into next

Pull HSI (High Speed Syncronous Interface) changes from Sebastian Reichel:
 "Please pull the following changes for the HSI subsystem, which I have
  taken over from Carlos Chinea <carlos.chinea@nokia.com>.

  The below patches have been worked on in the linux-omap mailinglist
  for 10 months and are well tested in linux-next (have been in there
  for more than two weeks) without any problems arising.  Apart from
  that potential regressions are very limited, because the subsystem is
  not yet used by any platform in the mainline kernel.

   - Add some documentation for the HSI subsystem

   - Add Device Tree support for the HSI subsystem

   - Add OMAP3 SSI driver (SSI is a legacy variant of HSI)

   - Add Nokia N900 Modem driver (without speech support for now)"

* tag 'hsi-for-3.16' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-hsi:
  HSI: Introduce Nokia N900 modem driver
  HSI: Introduce driver for SSI Protocol
  Documentation: DT: omap-ssi binding documentation
  HSI: Introduce OMAP SSI driver
  HSI: Add common DT binding for HSI client devices
  HSI: export method to (un)register clients
  HSI: Add channel resource support to HSI clients
  HSI: method to unregister clients from an hsi port
  HSI: hsi-char: fix driver for multiport scenarios
  MAINTAINERS: update HSI entry
  Documentation: HSI: Add some general description for the HSI subsystem
parents 6885a7f1 eafaebd9
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 */
};
};
};
Nokia modem client bindings
The Nokia modem HSI client follows the common HSI client binding
and inherits all required properties. The following additional
properties are needed by the Nokia modem HSI client:
Required properties:
- compatible: Should be one of
"nokia,n900-modem"
- hsi-channel-names: Should contain the following strings
"mcsaab-control"
"speech-control"
"speech-data"
"mcsaab-data"
- gpios: Should provide a GPIO handler for each GPIO listed in
gpio-names
- gpio-names: Should contain the following strings
"cmt_apeslpx"
"cmt_rst_rq"
"cmt_en"
"cmt_rst"
"cmt_bsi"
- interrupts: Should be IRQ handle for modem's reset indication
Example:
&ssi_port {
modem: hsi-client {
compatible = "nokia,n900-modem";
pinctrl-names = "default";
pinctrl-0 = <&modem_pins>;
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";
interrupts-extended = <&gpio3 8 IRQ_TYPE_EDGE_FALLING>; /* 72 */
gpios = <&gpio3 6 GPIO_ACTIVE_HIGH>, /* 70 */
<&gpio3 9 GPIO_ACTIVE_HIGH>, /* 73 */
<&gpio3 10 GPIO_ACTIVE_HIGH>, /* 74 */
<&gpio3 11 GPIO_ACTIVE_HIGH>, /* 75 */
<&gpio5 29 GPIO_ACTIVE_HIGH>; /* 157 */
gpio-names = "cmt_apeslpx",
"cmt_rst_rq",
"cmt_en",
"cmt_rst",
"cmt_bsi";
};
};
OMAP SSI controller bindings
OMAP Synchronous Serial Interface (SSI) controller implements a legacy
variant of MIPI's High Speed Synchronous Serial Interface (HSI).
Required properties:
- compatible: Should include "ti,omap3-ssi".
- reg-names: Contains the values "sys" and "gdd" (in this order).
- reg: Contains a matching register specifier for each entry
in reg-names.
- interrupt-names: Contains the value "gdd_mpu".
- interrupts: Contains matching interrupt information for each entry
in interrupt-names.
- ranges: Represents the bus address mapping between the main
controller node and the child nodes below.
- clock-names: Must include the following entries:
"ssi_ssr_fck": The OMAP clock of that name
"ssi_sst_fck": The OMAP clock of that name
"ssi_ick": The OMAP clock of that name
- clocks: Contains a matching clock specifier for each entry in
clock-names.
- #address-cells: Should be set to <1>
- #size-cells: Should be set to <1>
Each port is represented as a sub-node of the ti,omap3-ssi device.
Required Port sub-node properties:
- compatible: Should be set to the following value
ti,omap3-ssi-port (applicable to OMAP34xx devices)
- reg-names: Contains the values "tx" and "rx" (in this order).
- reg: Contains a matching register specifier for each entry
in reg-names.
- interrupt-parent Should be a phandle for the interrupt controller
- interrupts: Should contain interrupt specifiers for mpu interrupts
0 and 1 (in this order).
- ti,ssi-cawake-gpio: Defines which GPIO pin is used to signify CAWAKE
events for the port. This is an optional board-specific
property. If it's missing the port will not be
enabled.
Example for Nokia N900:
ssi-controller@48058000 {
compatible = "ti,omap3-ssi";
/* needed until hwmod is updated to use the compatible string */
ti,hwmods = "ssi";
reg = <0x48058000 0x1000>,
<0x48059000 0x1000>;
reg-names = "sys",
"gdd";
interrupts = <55>;
interrupt-names = "gdd_mpu";
clocks = <&ssi_ssr_fck>,
<&ssi_sst_fck>,
<&ssi_ick>;
clock-names = "ssi_ssr_fck",
"ssi_sst_fck",
"ssi_ick";
#address-cells = <1>;
#size-cells = <1>;
ranges;
ssi-port@4805a000 {
compatible = "ti,omap3-ssi-port";
reg = <0x4805a000 0x800>,
<0x4805a800 0x800>;
reg-names = "tx",
"rx";
interrupt-parent = <&intc>;
interrupts = <67>,
<68>;
ti,ssi-cawake-gpio = <&gpio5 23 GPIO_ACTIVE_HIGH>; /* 151 */
}
ssi-port@4805a000 {
compatible = "ti,omap3-ssi-port";
reg = <0x4805b000 0x800>,
<0x4805b800 0x800>;
reg-names = "tx",
"rx";
interrupt-parent = <&intc>;
interrupts = <69>,
<70>;
status = "disabled"; /* second port is not used on N900 */
}
}
HSI - High-speed Synchronous Serial Interface
1. Introduction
~~~~~~~~~~~~~~~
High Speed Syncronous Interface (HSI) is a fullduplex, low latency protocol,
that is optimized for die-level interconnect between an Application Processor
and a Baseband chipset. It has been specified by the MIPI alliance in 2003 and
implemented by multiple vendors since then.
The HSI interface supports full duplex communication over multiple channels
(typically 8) and is capable of reaching speeds up to 200 Mbit/s.
The serial protocol uses two signals, DATA and FLAG as combined data and clock
signals and an additional READY signal for flow control. An additional WAKE
signal can be used to wakeup the chips from standby modes. The signals are
commonly prefixed by AC for signals going from the application die to the
cellular die and CA for signals going the other way around.
+------------+ +---------------+
| Cellular | | Application |
| Die | | Die |
| | - - - - - - CAWAKE - - - - - - >| |
| T|------------ CADATA ------------>|R |
| X|------------ CAFLAG ------------>|X |
| |<----------- ACREADY ------------| |
| | | |
| | | |
| |< - - - - - ACWAKE - - - - - - -| |
| R|<----------- ACDATA -------------|T |
| X|<----------- ACFLAG -------------|X |
| |------------ CAREADY ----------->| |
| | | |
| | | |
+------------+ +---------------+
2. HSI Subsystem in Linux
~~~~~~~~~~~~~~~~~~~~~~~~~
In the Linux kernel the hsi subsystem is supposed to be used for HSI devices.
The hsi subsystem contains drivers for hsi controllers including support for
multi-port controllers and provides a generic API for using the HSI ports.
It also contains HSI client drivers, which make use of the generic API to
implement a protocol used on the HSI interface. These client drivers can
use an arbitrary number of channels.
3. hsi-char Device
~~~~~~~~~~~~~~~~~~
Each port automatically registers a generic client driver called hsi_char,
which provides a charecter device for userspace representing the HSI port.
It can be used to communicate via HSI from userspace. Userspace may
configure the hsi_char device using the following ioctl commands:
* HSC_RESET:
- flush the HSI port
* HSC_SET_PM
- enable or disable the client.
* HSC_SEND_BREAK
- send break
* HSC_SET_RX
- set RX configuration
* HSC_GET_RX
- get RX configuration
* HSC_SET_TX
- set TX configuration
* HSC_GET_TX
- get TX configuration
...@@ -4204,9 +4204,11 @@ S: Maintained ...@@ -4204,9 +4204,11 @@ S: Maintained
F: fs/hpfs/ F: fs/hpfs/
HSI SUBSYSTEM HSI SUBSYSTEM
M: Sebastian Reichel <sre@debian.org> M: Sebastian Reichel <sre@kernel.org>
T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-hsi.git
S: Maintained S: Maintained
F: Documentation/ABI/testing/sysfs-bus-hsi F: Documentation/ABI/testing/sysfs-bus-hsi
F: Documentation/hsi.txt
F: drivers/hsi/ F: drivers/hsi/
F: include/linux/hsi/ F: include/linux/hsi/
F: include/uapi/linux/hsi/ F: include/uapi/linux/hsi/
......
...@@ -14,6 +14,7 @@ config HSI_BOARDINFO ...@@ -14,6 +14,7 @@ config HSI_BOARDINFO
bool bool
default y default y
source "drivers/hsi/controllers/Kconfig"
source "drivers/hsi/clients/Kconfig" source "drivers/hsi/clients/Kconfig"
endif # HSI endif # HSI
...@@ -3,4 +3,5 @@ ...@@ -3,4 +3,5 @@
# #
obj-$(CONFIG_HSI_BOARDINFO) += hsi_boardinfo.o obj-$(CONFIG_HSI_BOARDINFO) += hsi_boardinfo.o
obj-$(CONFIG_HSI) += hsi.o obj-$(CONFIG_HSI) += hsi.o
obj-y += controllers/
obj-y += clients/ obj-y += clients/
...@@ -4,6 +4,23 @@ ...@@ -4,6 +4,23 @@
comment "HSI clients" comment "HSI clients"
config NOKIA_MODEM
tristate "Nokia Modem"
depends on HSI && SSI_PROTOCOL
help
Say Y here if you want to add support for the modem on Nokia
N900 (Nokia RX-51) hardware.
If unsure, say N.
config SSI_PROTOCOL
tristate "SSI protocol"
depends on HSI && PHONET && (OMAP_SSI=y || OMAP_SSI=m)
help
If you say Y here, you will enable the SSI protocol aka McSAAB.
If unsure, say N.
config HSI_CHAR config HSI_CHAR
tristate "HSI/SSI character driver" tristate "HSI/SSI character driver"
depends on HSI depends on HSI
......
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
# Makefile for HSI clients # Makefile for HSI clients
# #
obj-$(CONFIG_NOKIA_MODEM) += nokia-modem.o
obj-$(CONFIG_SSI_PROTOCOL) += ssi_protocol.o
obj-$(CONFIG_HSI_CHAR) += hsi_char.o obj-$(CONFIG_HSI_CHAR) += hsi_char.o
...@@ -367,7 +367,7 @@ static int hsc_rx_set(struct hsi_client *cl, struct hsc_rx_config *rxc) ...@@ -367,7 +367,7 @@ static int hsc_rx_set(struct hsi_client *cl, struct hsc_rx_config *rxc)
return -EINVAL; return -EINVAL;
tmp = cl->rx_cfg; tmp = cl->rx_cfg;
cl->rx_cfg.mode = rxc->mode; cl->rx_cfg.mode = rxc->mode;
cl->rx_cfg.channels = rxc->channels; cl->rx_cfg.num_hw_channels = rxc->channels;
cl->rx_cfg.flow = rxc->flow; cl->rx_cfg.flow = rxc->flow;
ret = hsi_setup(cl); ret = hsi_setup(cl);
if (ret < 0) { if (ret < 0) {
...@@ -383,7 +383,7 @@ static int hsc_rx_set(struct hsi_client *cl, struct hsc_rx_config *rxc) ...@@ -383,7 +383,7 @@ static int hsc_rx_set(struct hsi_client *cl, struct hsc_rx_config *rxc)
static inline void hsc_rx_get(struct hsi_client *cl, struct hsc_rx_config *rxc) static inline void hsc_rx_get(struct hsi_client *cl, struct hsc_rx_config *rxc)
{ {
rxc->mode = cl->rx_cfg.mode; rxc->mode = cl->rx_cfg.mode;
rxc->channels = cl->rx_cfg.channels; rxc->channels = cl->rx_cfg.num_hw_channels;
rxc->flow = cl->rx_cfg.flow; rxc->flow = cl->rx_cfg.flow;
} }
...@@ -402,7 +402,7 @@ static int hsc_tx_set(struct hsi_client *cl, struct hsc_tx_config *txc) ...@@ -402,7 +402,7 @@ static int hsc_tx_set(struct hsi_client *cl, struct hsc_tx_config *txc)
return -EINVAL; return -EINVAL;
tmp = cl->tx_cfg; tmp = cl->tx_cfg;
cl->tx_cfg.mode = txc->mode; cl->tx_cfg.mode = txc->mode;
cl->tx_cfg.channels = txc->channels; cl->tx_cfg.num_hw_channels = txc->channels;
cl->tx_cfg.speed = txc->speed; cl->tx_cfg.speed = txc->speed;
cl->tx_cfg.arb_mode = txc->arb_mode; cl->tx_cfg.arb_mode = txc->arb_mode;
ret = hsi_setup(cl); ret = hsi_setup(cl);
...@@ -417,7 +417,7 @@ static int hsc_tx_set(struct hsi_client *cl, struct hsc_tx_config *txc) ...@@ -417,7 +417,7 @@ static int hsc_tx_set(struct hsi_client *cl, struct hsc_tx_config *txc)
static inline void hsc_tx_get(struct hsi_client *cl, struct hsc_tx_config *txc) static inline void hsc_tx_get(struct hsi_client *cl, struct hsc_tx_config *txc)
{ {
txc->mode = cl->tx_cfg.mode; txc->mode = cl->tx_cfg.mode;
txc->channels = cl->tx_cfg.channels; txc->channels = cl->tx_cfg.num_hw_channels;
txc->speed = cl->tx_cfg.speed; txc->speed = cl->tx_cfg.speed;
txc->arb_mode = cl->tx_cfg.arb_mode; txc->arb_mode = cl->tx_cfg.arb_mode;
} }
...@@ -435,7 +435,7 @@ static ssize_t hsc_read(struct file *file, char __user *buf, size_t len, ...@@ -435,7 +435,7 @@ static ssize_t hsc_read(struct file *file, char __user *buf, size_t len,
return -EINVAL; return -EINVAL;
if (len > max_data_size) if (len > max_data_size)
len = max_data_size; len = max_data_size;
if (channel->ch >= channel->cl->rx_cfg.channels) if (channel->ch >= channel->cl->rx_cfg.num_hw_channels)
return -ECHRNG; return -ECHRNG;
if (test_and_set_bit(HSC_CH_READ, &channel->flags)) if (test_and_set_bit(HSC_CH_READ, &channel->flags))
return -EBUSY; return -EBUSY;
...@@ -492,7 +492,7 @@ static ssize_t hsc_write(struct file *file, const char __user *buf, size_t len, ...@@ -492,7 +492,7 @@ static ssize_t hsc_write(struct file *file, const char __user *buf, size_t len,
return -EINVAL; return -EINVAL;
if (len > max_data_size) if (len > max_data_size)
len = max_data_size; len = max_data_size;
if (channel->ch >= channel->cl->tx_cfg.channels) if (channel->ch >= channel->cl->tx_cfg.num_hw_channels)
return -ECHRNG; return -ECHRNG;
if (test_and_set_bit(HSC_CH_WRITE, &channel->flags)) if (test_and_set_bit(HSC_CH_WRITE, &channel->flags))
return -EBUSY; return -EBUSY;
...@@ -705,7 +705,7 @@ static int hsc_probe(struct device *dev) ...@@ -705,7 +705,7 @@ static int hsc_probe(struct device *dev)
if (!hsc_major) { if (!hsc_major) {
ret = alloc_chrdev_region(&hsc_dev, hsc_baseminor, ret = alloc_chrdev_region(&hsc_dev, hsc_baseminor,
HSC_DEVS, devname); HSC_DEVS, devname);
if (ret > 0) if (ret == 0)
hsc_major = MAJOR(hsc_dev); hsc_major = MAJOR(hsc_dev);
} else { } else {
hsc_dev = MKDEV(hsc_major, hsc_baseminor); hsc_dev = MKDEV(hsc_major, hsc_baseminor);
......
/*
* nokia-modem.c
*
* HSI client driver for Nokia N900 modem.
*
* Copyright (C) 2014 Sebastian Reichel <sre@kernel.org>
*
* 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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/gpio/consumer.h>
#include <linux/hsi/hsi.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/hsi/ssi_protocol.h>
static unsigned int pm;
module_param(pm, int, 0400);
MODULE_PARM_DESC(pm,
"Enable power management (0=disabled, 1=userland based [default])");
struct nokia_modem_gpio {
struct gpio_desc *gpio;
const char *name;
};
struct nokia_modem_device {
struct tasklet_struct nokia_modem_rst_ind_tasklet;
int nokia_modem_rst_ind_irq;
struct device *device;
struct nokia_modem_gpio *gpios;
int gpio_amount;
struct hsi_client *ssi_protocol;
};
static void do_nokia_modem_rst_ind_tasklet(unsigned long data)
{
struct nokia_modem_device *modem = (struct nokia_modem_device *)data;
if (!modem)
return;
dev_info(modem->device, "CMT rst line change detected\n");
if (modem->ssi_protocol)
ssip_reset_event(modem->ssi_protocol);
}
static irqreturn_t nokia_modem_rst_ind_isr(int irq, void *data)
{
struct nokia_modem_device *modem = (struct nokia_modem_device *)data;
tasklet_schedule(&modem->nokia_modem_rst_ind_tasklet);
return IRQ_HANDLED;
}
static void nokia_modem_gpio_unexport(struct device *dev)
{
struct nokia_modem_device *modem = dev_get_drvdata(dev);
int i;
for (i = 0; i < modem->gpio_amount; i++) {
sysfs_remove_link(&dev->kobj, modem->gpios[i].name);
gpiod_unexport(modem->gpios[i].gpio);
}
}
static int nokia_modem_gpio_probe(struct device *dev)
{
struct device_node *np = dev->of_node;
struct nokia_modem_device *modem = dev_get_drvdata(dev);
int gpio_count, gpio_name_count, i, err;
gpio_count = of_gpio_count(np);
if (gpio_count < 0) {
dev_err(dev, "missing gpios: %d\n", gpio_count);
return gpio_count;
}
gpio_name_count = of_property_count_strings(np, "gpio-names");
if (gpio_count != gpio_name_count) {
dev_err(dev, "number of gpios does not equal number of gpio names\n");
return -EINVAL;
}
modem->gpios = devm_kzalloc(dev, gpio_count *
sizeof(struct nokia_modem_gpio), GFP_KERNEL);
if (!modem->gpios) {
dev_err(dev, "Could not allocate memory for gpios\n");
return -ENOMEM;
}
modem->gpio_amount = gpio_count;
for (i = 0; i < gpio_count; i++) {
modem->gpios[i].gpio = devm_gpiod_get_index(dev, NULL, i);
if (IS_ERR(modem->gpios[i].gpio)) {
dev_err(dev, "Could not get gpio %d\n", i);
return PTR_ERR(modem->gpios[i].gpio);
}
err = of_property_read_string_index(np, "gpio-names", i,
&(modem->gpios[i].name));
if (err) {
dev_err(dev, "Could not get gpio name %d\n", i);
return err;
}
err = gpiod_direction_output(modem->gpios[i].gpio, 0);
if (err)
return err;
err = gpiod_export(modem->gpios[i].gpio, 0);
if (err)
return err;
err = gpiod_export_link(dev, modem->gpios[i].name,
modem->gpios[i].gpio);
if (err)
return err;
}
return 0;
}
static int nokia_modem_probe(struct device *dev)
{
struct device_node *np;
struct nokia_modem_device *modem;
struct hsi_client *cl = to_hsi_client(dev);
struct hsi_port *port = hsi_get_port(cl);
int irq, pflags, err;
struct hsi_board_info ssip;
np = dev->of_node;
if (!np) {
dev_err(dev, "device tree node not found\n");
return -ENXIO;
}
modem = devm_kzalloc(dev, sizeof(*modem), GFP_KERNEL);
if (!modem) {
dev_err(dev, "Could not allocate memory for nokia_modem_device\n");
return -ENOMEM;
}
dev_set_drvdata(dev, modem);
irq = irq_of_parse_and_map(np, 0);
if (irq < 0) {
dev_err(dev, "Invalid rst_ind interrupt (%d)\n", irq);
return irq;
}
modem->nokia_modem_rst_ind_irq = irq;
pflags = irq_get_trigger_type(irq);
tasklet_init(&modem->nokia_modem_rst_ind_tasklet,
do_nokia_modem_rst_ind_tasklet, (unsigned long)modem);
err = devm_request_irq(dev, irq, nokia_modem_rst_ind_isr,
IRQF_DISABLED | pflags, "modem_rst_ind", modem);
if (err < 0) {
dev_err(dev, "Request rst_ind irq(%d) failed (flags %d)\n",
irq, pflags);
return err;
}
enable_irq_wake(irq);
if(pm) {
err = nokia_modem_gpio_probe(dev);
if (err < 0) {
dev_err(dev, "Could not probe GPIOs\n");
goto error1;
}
}
ssip.name = "ssi-protocol";
ssip.tx_cfg = cl->tx_cfg;
ssip.rx_cfg = cl->rx_cfg;
ssip.platform_data = NULL;
ssip.archdata = NULL;
modem->ssi_protocol = hsi_new_client(port, &ssip);
if (!modem->ssi_protocol) {
dev_err(dev, "Could not register ssi-protocol device\n");
goto error2;
}
err = device_attach(&modem->ssi_protocol->device);
if (err == 0) {
dev_err(dev, "Missing ssi-protocol driver\n");
err = -EPROBE_DEFER;
goto error3;
} else if (err < 0) {
dev_err(dev, "Could not load ssi-protocol driver (%d)\n", err);
goto error3;
}
/* TODO: register cmt-speech hsi client */
dev_info(dev, "Registered Nokia HSI modem\n");
return 0;
error3:
hsi_remove_client(&modem->ssi_protocol->device, NULL);
error2:
nokia_modem_gpio_unexport(dev);
error1:
disable_irq_wake(modem->nokia_modem_rst_ind_irq);
tasklet_kill(&modem->nokia_modem_rst_ind_tasklet);
return err;
}
static int nokia_modem_remove(struct device *dev)
{
struct nokia_modem_device *modem = dev_get_drvdata(dev);
if (!modem)
return 0;
if (modem->ssi_protocol) {
hsi_remove_client(&modem->ssi_protocol->device, NULL);
modem->ssi_protocol = NULL;
}
nokia_modem_gpio_unexport(dev);
dev_set_drvdata(dev, NULL);
disable_irq_wake(modem->nokia_modem_rst_ind_irq);
tasklet_kill(&modem->nokia_modem_rst_ind_tasklet);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id nokia_modem_of_match[] = {
{ .compatible = "nokia,n900-modem", },
{},
};
MODULE_DEVICE_TABLE(of, nokia_modem_of_match);
#endif
static struct hsi_client_driver nokia_modem_driver = {
.driver = {
.name = "nokia-modem",
.owner = THIS_MODULE,
.probe = nokia_modem_probe,
.remove = nokia_modem_remove,
.of_match_table = of_match_ptr(nokia_modem_of_match),
},
};
static int __init nokia_modem_init(void)
{
return hsi_register_client_driver(&nokia_modem_driver);
}
module_init(nokia_modem_init);
static void __exit nokia_modem_exit(void)
{
hsi_unregister_client_driver(&nokia_modem_driver);
}
module_exit(nokia_modem_exit);
MODULE_ALIAS("hsi:nokia-modem");
MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
MODULE_DESCRIPTION("HSI driver module for Nokia N900 Modem");
MODULE_LICENSE("GPL");
This diff is collapsed.
#
# HSI controllers configuration
#
comment "HSI controllers"
config OMAP_SSI
tristate "OMAP SSI hardware driver"
depends on HSI && OF && (ARCH_OMAP3 || (ARM && COMPILE_TEST))
---help---
SSI is a legacy version of HSI. It is usually used to connect
an application engine with a cellular modem.
If you say Y here, you will enable the OMAP SSI hardware driver.
If unsure, say N.
config OMAP_SSI_PORT
tristate
default m if OMAP_SSI=m
default y if OMAP_SSI=y
#
# Makefile for HSI controllers drivers
#
obj-$(CONFIG_OMAP_SSI) += omap_ssi.o
obj-$(CONFIG_OMAP_SSI_PORT) += omap_ssi_port.o
This diff is collapsed.
/* OMAP SSI internal interface.
*
* Copyright (C) 2010 Nokia Corporation. All rights reserved.
* Copyright (C) 2013 Sebastian Reichel
*
* Contact: Carlos Chinea <carlos.chinea@nokia.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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef __LINUX_HSI_OMAP_SSI_H__
#define __LINUX_HSI_OMAP_SSI_H__
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/hsi/hsi.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#define SSI_MAX_CHANNELS 8
#define SSI_MAX_GDD_LCH 8
#define SSI_BYTES_TO_FRAMES(x) ((((x) - 1) >> 2) + 1)
/**
* struct omap_ssm_ctx - OMAP synchronous serial module (TX/RX) context
* @mode: Bit transmission mode
* @channels: Number of channels
* @framesize: Frame size in bits
* @timeout: RX frame timeout
* @divisor: TX divider
* @arb_mode: Arbitration mode for TX frame (Round robin, priority)
*/
struct omap_ssm_ctx {
u32 mode;
u32 channels;
u32 frame_size;
union {
u32 timeout; /* Rx Only */
struct {
u32 arb_mode;
u32 divisor;
}; /* Tx only */
};
};
/**
* struct omap_ssi_port - OMAP SSI port data
* @dev: device associated to the port (HSI port)
* @pdev: platform device associated to the port
* @sst_dma: SSI transmitter physical base address
* @ssr_dma: SSI receiver physical base address
* @sst_base: SSI transmitter base address
* @ssr_base: SSI receiver base address
* @wk_lock: spin lock to serialize access to the wake lines
* @lock: Spin lock to serialize access to the SSI port
* @channels: Current number of channels configured (1,2,4 or 8)
* @txqueue: TX message queues
* @rxqueue: RX message queues
* @brkqueue: Queue of incoming HWBREAK requests (FRAME mode)
* @irq: IRQ number
* @wake_irq: IRQ number for incoming wake line (-1 if none)
* @wake_gpio: GPIO number for incoming wake line (-1 if none)
* @pio_tasklet: Bottom half for PIO transfers and events
* @wake_tasklet: Bottom half for incoming wake events
* @wkin_cken: Keep track of clock references due to the incoming wake line
* @wk_refcount: Reference count for output wake line
* @sys_mpu_enable: Context for the interrupt enable register for irq 0
* @sst: Context for the synchronous serial transmitter
* @ssr: Context for the synchronous serial receiver
*/
struct omap_ssi_port {
struct device *dev;
struct device *pdev;
dma_addr_t sst_dma;
dma_addr_t ssr_dma;
void __iomem *sst_base;
void __iomem *ssr_base;
spinlock_t wk_lock;
spinlock_t lock;
unsigned int channels;
struct list_head txqueue[SSI_MAX_CHANNELS];
struct list_head rxqueue[SSI_MAX_CHANNELS];
struct list_head brkqueue;
unsigned int irq;
int wake_irq;
int wake_gpio;
struct tasklet_struct pio_tasklet;
struct tasklet_struct wake_tasklet;
bool wktest:1; /* FIXME: HACK to be removed */
bool wkin_cken:1; /* Workaround */
unsigned int wk_refcount;
/* OMAP SSI port context */
u32 sys_mpu_enable; /* We use only one irq */
struct omap_ssm_ctx sst;
struct omap_ssm_ctx ssr;
u32 loss_count;
u32 port_id;
#ifdef CONFIG_DEBUG_FS
struct dentry *dir;
#endif
};
/**
* struct gdd_trn - GDD transaction data
* @msg: Pointer to the HSI message being served
* @sg: Pointer to the current sg entry being served
*/
struct gdd_trn {
struct hsi_msg *msg;
struct scatterlist *sg;
};
/**
* struct omap_ssi_controller - OMAP SSI controller data
* @dev: device associated to the controller (HSI controller)
* @sys: SSI I/O base address
* @gdd: GDD I/O base address
* @fck: SSI functional clock
* @gdd_irq: IRQ line for GDD
* @gdd_tasklet: bottom half for DMA transfers
* @gdd_trn: Array of GDD transaction data for ongoing GDD transfers
* @lock: lock to serialize access to GDD
* @loss_count: To follow if we need to restore context or not
* @max_speed: Maximum TX speed (Kb/s) set by the clients.
* @sysconfig: SSI controller saved context
* @gdd_gcr: SSI GDD saved context
* @get_loss: Pointer to omap_pm_get_dev_context_loss_count, if any
* @port: Array of pointers of the ports of the controller
* @dir: Debugfs SSI root directory
*/
struct omap_ssi_controller {
struct device *dev;
void __iomem *sys;
void __iomem *gdd;
struct clk *fck;
unsigned int gdd_irq;
struct tasklet_struct gdd_tasklet;
struct gdd_trn gdd_trn[SSI_MAX_GDD_LCH];
spinlock_t lock;
unsigned long fck_rate;
u32 loss_count;
u32 max_speed;
/* OMAP SSI Controller context */
u32 sysconfig;
u32 gdd_gcr;
int (*get_loss)(struct device *dev);
struct omap_ssi_port **port;
#ifdef CONFIG_DEBUG_FS
struct dentry *dir;
#endif
};
#endif /* __LINUX_HSI_OMAP_SSI_H__ */
This diff is collapsed.
/* Hardware definitions for SSI.
*
* Copyright (C) 2010 Nokia Corporation. All rights reserved.
*
* Contact: Carlos Chinea <carlos.chinea@nokia.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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef __OMAP_SSI_REGS_H__
#define __OMAP_SSI_REGS_H__
/*
* SSI SYS registers
*/
#define SSI_REVISION_REG 0
# define SSI_REV_MAJOR 0xf0
# define SSI_REV_MINOR 0xf
#define SSI_SYSCONFIG_REG 0x10
# define SSI_AUTOIDLE (1 << 0)
# define SSI_SOFTRESET (1 << 1)
# define SSI_SIDLEMODE_FORCE 0
# define SSI_SIDLEMODE_NO (1 << 3)
# define SSI_SIDLEMODE_SMART (1 << 4)
# define SSI_SIDLEMODE_MASK 0x18
# define SSI_MIDLEMODE_FORCE 0
# define SSI_MIDLEMODE_NO (1 << 12)
# define SSI_MIDLEMODE_SMART (1 << 13)
# define SSI_MIDLEMODE_MASK 0x3000
#define SSI_SYSSTATUS_REG 0x14
# define SSI_RESETDONE 1
#define SSI_MPU_STATUS_REG(port, irq) (0x808 + ((port) * 0x10) + ((irq) * 2))
#define SSI_MPU_ENABLE_REG(port, irq) (0x80c + ((port) * 0x10) + ((irq) * 8))
# define SSI_DATAACCEPT(channel) (1 << (channel))
# define SSI_DATAAVAILABLE(channel) (1 << ((channel) + 8))
# define SSI_DATAOVERRUN(channel) (1 << ((channel) + 16))
# define SSI_ERROROCCURED (1 << 24)
# define SSI_BREAKDETECTED (1 << 25)
#define SSI_GDD_MPU_IRQ_STATUS_REG 0x0800
#define SSI_GDD_MPU_IRQ_ENABLE_REG 0x0804
# define SSI_GDD_LCH(channel) (1 << (channel))
#define SSI_WAKE_REG(port) (0xc00 + ((port) * 0x10))
#define SSI_CLEAR_WAKE_REG(port) (0xc04 + ((port) * 0x10))
#define SSI_SET_WAKE_REG(port) (0xc08 + ((port) * 0x10))
# define SSI_WAKE(channel) (1 << (channel))
# define SSI_WAKE_MASK 0xff
/*
* SSI SST registers
*/
#define SSI_SST_ID_REG 0
#define SSI_SST_MODE_REG 4
# define SSI_MODE_VAL_MASK 3
# define SSI_MODE_SLEEP 0
# define SSI_MODE_STREAM 1
# define SSI_MODE_FRAME 2
# define SSI_MODE_MULTIPOINTS 3
#define SSI_SST_FRAMESIZE_REG 8
# define SSI_FRAMESIZE_DEFAULT 31
#define SSI_SST_TXSTATE_REG 0xc
# define SSI_TXSTATE_IDLE 0
#define SSI_SST_BUFSTATE_REG 0x10
# define SSI_FULL(channel) (1 << (channel))
#define SSI_SST_DIVISOR_REG 0x18
# define SSI_MAX_DIVISOR 127
#define SSI_SST_BREAK_REG 0x20
#define SSI_SST_CHANNELS_REG 0x24
# define SSI_CHANNELS_DEFAULT 4
#define SSI_SST_ARBMODE_REG 0x28
# define SSI_ARBMODE_ROUNDROBIN 0
# define SSI_ARBMODE_PRIORITY 1
#define SSI_SST_BUFFER_CH_REG(channel) (0x80 + ((channel) * 4))
#define SSI_SST_SWAPBUF_CH_REG(channel) (0xc0 + ((channel) * 4))
/*
* SSI SSR registers
*/
#define SSI_SSR_ID_REG 0
#define SSI_SSR_MODE_REG 4
#define SSI_SSR_FRAMESIZE_REG 8
#define SSI_SSR_RXSTATE_REG 0xc
#define SSI_SSR_BUFSTATE_REG 0x10
# define SSI_NOTEMPTY(channel) (1 << (channel))
#define SSI_SSR_BREAK_REG 0x1c
#define SSI_SSR_ERROR_REG 0x20
#define SSI_SSR_ERRORACK_REG 0x24
#define SSI_SSR_OVERRUN_REG 0x2c
#define SSI_SSR_OVERRUNACK_REG 0x30
#define SSI_SSR_TIMEOUT_REG 0x34
# define SSI_TIMEOUT_DEFAULT 0
#define SSI_SSR_CHANNELS_REG 0x28
#define SSI_SSR_BUFFER_CH_REG(channel) (0x80 + ((channel) * 4))
#define SSI_SSR_SWAPBUF_CH_REG(channel) (0xc0 + ((channel) * 4))
/*
* SSI GDD registers
*/
#define SSI_GDD_HW_ID_REG 0
#define SSI_GDD_PPORT_ID_REG 0x10
#define SSI_GDD_MPORT_ID_REG 0x14
#define SSI_GDD_PPORT_SR_REG 0x20
#define SSI_GDD_MPORT_SR_REG 0x24
# define SSI_ACTIVE_LCH_NUM_MASK 0xff
#define SSI_GDD_TEST_REG 0x40
# define SSI_TEST 1
#define SSI_GDD_GCR_REG 0x100
# define SSI_CLK_AUTOGATING_ON (1 << 3)
# define SSI_FREE (1 << 2)
# define SSI_SWITCH_OFF (1 << 0)
#define SSI_GDD_GRST_REG 0x200
# define SSI_SWRESET 1
#define SSI_GDD_CSDP_REG(channel) (0x800 + ((channel) * 0x40))
# define SSI_DST_BURST_EN_MASK 0xc000
# define SSI_DST_SINGLE_ACCESS0 0
# define SSI_DST_SINGLE_ACCESS (1 << 14)
# define SSI_DST_BURST_4x32_BIT (2 << 14)
# define SSI_DST_BURST_8x32_BIT (3 << 14)
# define SSI_DST_MASK 0x1e00
# define SSI_DST_MEMORY_PORT (8 << 9)
# define SSI_DST_PERIPHERAL_PORT (9 << 9)
# define SSI_SRC_BURST_EN_MASK 0x180
# define SSI_SRC_SINGLE_ACCESS0 0
# define SSI_SRC_SINGLE_ACCESS (1 << 7)
# define SSI_SRC_BURST_4x32_BIT (2 << 7)
# define SSI_SRC_BURST_8x32_BIT (3 << 7)
# define SSI_SRC_MASK 0x3c
# define SSI_SRC_MEMORY_PORT (8 << 2)
# define SSI_SRC_PERIPHERAL_PORT (9 << 2)
# define SSI_DATA_TYPE_MASK 3
# define SSI_DATA_TYPE_S32 2
#define SSI_GDD_CCR_REG(channel) (0x802 + ((channel) * 0x40))
# define SSI_DST_AMODE_MASK (3 << 14)
# define SSI_DST_AMODE_CONST 0
# define SSI_DST_AMODE_POSTINC (1 << 12)
# define SSI_SRC_AMODE_MASK (3 << 12)
# define SSI_SRC_AMODE_CONST 0
# define SSI_SRC_AMODE_POSTINC (1 << 12)
# define SSI_CCR_ENABLE (1 << 7)
# define SSI_CCR_SYNC_MASK 0x1f
#define SSI_GDD_CICR_REG(channel) (0x804 + ((channel) * 0x40))
# define SSI_BLOCK_IE (1 << 5)
# define SSI_HALF_IE (1 << 2)
# define SSI_TOUT_IE (1 << 0)
#define SSI_GDD_CSR_REG(channel) (0x806 + ((channel) * 0x40))
# define SSI_CSR_SYNC (1 << 6)
# define SSI_CSR_BLOCK (1 << 5)
# define SSI_CSR_HALF (1 << 2)
# define SSI_CSR_TOUR (1 << 0)
#define SSI_GDD_CSSA_REG(channel) (0x808 + ((channel) * 0x40))
#define SSI_GDD_CDSA_REG(channel) (0x80c + ((channel) * 0x40))
#define SSI_GDD_CEN_REG(channel) (0x810 + ((channel) * 0x40))
#define SSI_GDD_CSAC_REG(channel) (0x818 + ((channel) * 0x40))
#define SSI_GDD_CDAC_REG(channel) (0x81a + ((channel) * 0x40))
#define SSI_GDD_CLNK_CTRL_REG(channel) (0x828 + ((channel) * 0x40))
# define SSI_ENABLE_LNK (1 << 15)
# define SSI_STOP_LNK (1 << 14)
# define SSI_NEXT_CH_ID_MASK 0xf
#endif /* __OMAP_SSI_REGS_H__ */
...@@ -26,6 +26,8 @@ ...@@ -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) ...@@ -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 = {
...@@ -62,18 +70,37 @@ static struct bus_type hsi_bus_type = { ...@@ -62,18 +70,37 @@ static struct bus_type hsi_bus_type = {
static void hsi_client_release(struct device *dev) static void hsi_client_release(struct device *dev)
{ {
kfree(to_hsi_client(dev)); struct hsi_client *cl = to_hsi_client(dev);
kfree(cl->tx_cfg.channels);
kfree(cl->rx_cfg.channels);
kfree(cl);
} }
static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info) struct hsi_client *hsi_new_client(struct hsi_port *port,
struct hsi_board_info *info)
{ {
struct hsi_client *cl; struct hsi_client *cl;
size_t size;
cl = kzalloc(sizeof(*cl), GFP_KERNEL); cl = kzalloc(sizeof(*cl), GFP_KERNEL);
if (!cl) if (!cl)
return; return NULL;
cl->tx_cfg = info->tx_cfg; cl->tx_cfg = info->tx_cfg;
if (cl->tx_cfg.channels) {
size = cl->tx_cfg.num_channels * sizeof(*cl->tx_cfg.channels);
cl->tx_cfg.channels = kzalloc(size , GFP_KERNEL);
memcpy(cl->tx_cfg.channels, info->tx_cfg.channels, size);
}
cl->rx_cfg = info->rx_cfg; cl->rx_cfg = info->rx_cfg;
if (cl->rx_cfg.channels) {
size = cl->rx_cfg.num_channels * sizeof(*cl->rx_cfg.channels);
cl->rx_cfg.channels = kzalloc(size , GFP_KERNEL);
memcpy(cl->rx_cfg.channels, info->rx_cfg.channels, size);
}
cl->device.bus = &hsi_bus_type; cl->device.bus = &hsi_bus_type;
cl->device.parent = &port->device; cl->device.parent = &port->device;
cl->device.release = hsi_client_release; cl->device.release = hsi_client_release;
...@@ -85,7 +112,10 @@ static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info) ...@@ -85,7 +112,10 @@ static void hsi_new_client(struct hsi_port *port, struct hsi_board_info *info)
pr_err("hsi: failed to register client: %s\n", info->name); pr_err("hsi: failed to register client: %s\n", info->name);
put_device(&cl->device); put_device(&cl->device);
} }
return cl;
} }
EXPORT_SYMBOL_GPL(hsi_new_client);
static void hsi_scan_board_info(struct hsi_controller *hsi) static void hsi_scan_board_info(struct hsi_controller *hsi)
{ {
...@@ -101,12 +131,209 @@ static void hsi_scan_board_info(struct hsi_controller *hsi) ...@@ -101,12 +131,209 @@ static void hsi_scan_board_info(struct hsi_controller *hsi)
} }
} }
static int hsi_remove_client(struct device *dev, void *data __maybe_unused) #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); device_unregister(dev);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(hsi_remove_client);
static int hsi_remove_port(struct device *dev, void *data __maybe_unused) static int hsi_remove_port(struct device *dev, void *data __maybe_unused)
{ {
...@@ -129,6 +356,16 @@ static void hsi_port_release(struct device *dev) ...@@ -129,6 +356,16 @@ static void hsi_port_release(struct device *dev)
kfree(to_hsi_port(dev)); kfree(to_hsi_port(dev));
} }
/**
* hsi_unregister_port - Unregister an HSI port
* @port: The HSI port to unregister
*/
void hsi_port_unregister_clients(struct hsi_port *port)
{
device_for_each_child(&port->device, NULL, hsi_remove_client);
}
EXPORT_SYMBOL_GPL(hsi_port_unregister_clients);
/** /**
* hsi_unregister_controller - Unregister an HSI controller * hsi_unregister_controller - Unregister an HSI controller
* @hsi: The HSI controller to register * @hsi: The HSI controller to register
...@@ -472,7 +709,7 @@ int hsi_unregister_port_event(struct hsi_client *cl) ...@@ -472,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
* *
...@@ -492,6 +729,32 @@ int hsi_event(struct hsi_port *port, unsigned long event) ...@@ -492,6 +729,32 @@ int hsi_event(struct hsi_port *port, unsigned long event)
} }
EXPORT_SYMBOL_GPL(hsi_event); EXPORT_SYMBOL_GPL(hsi_event);
/**
* hsi_get_channel_id_by_name - acquire channel id by channel name
* @cl: HSI client, which uses the channel
* @name: name the channel is known under
*
* Clients can call this function to get the hsi channel ids similar to
* requesting IRQs or GPIOs by name. This function assumes the same
* channel configuration is used for RX and TX.
*
* Returns -errno on error or channel id on success.
*/
int hsi_get_channel_id_by_name(struct hsi_client *cl, char *name)
{
int i;
if (!cl->rx_cfg.channels)
return -ENOENT;
for (i = 0; i < cl->rx_cfg.num_channels; i++)
if (!strcmp(cl->rx_cfg.channels[i].name, name))
return cl->rx_cfg.channels[i].id;
return -ENXIO;
}
EXPORT_SYMBOL_GPL(hsi_get_channel_id_by_name);
static int __init hsi_init(void) static int __init hsi_init(void)
{ {
return bus_register(&hsi_bus_type); return bus_register(&hsi_bus_type);
......
...@@ -67,17 +67,31 @@ enum { ...@@ -67,17 +67,31 @@ enum {
HSI_EVENT_STOP_RX, HSI_EVENT_STOP_RX,
}; };
/**
* struct hsi_channel - channel resource used by the hsi clients
* @id: Channel number
* @name: Channel name
*/
struct hsi_channel {
unsigned int id;
const char *name;
};
/** /**
* struct hsi_config - Configuration for RX/TX HSI modules * struct hsi_config - Configuration for RX/TX HSI modules
* @mode: Bit transmission mode (STREAM or FRAME) * @mode: Bit transmission mode (STREAM or FRAME)
* @channels: Number of channels to use [1..16] * @channels: Channel resources used by the client
* @num_channels: Number of channel resources
* @num_hw_channels: Number of channels the transceiver is configured for [1..16]
* @speed: Max bit transmission speed (Kbit/s) * @speed: Max bit transmission speed (Kbit/s)
* @flow: RX flow type (SYNCHRONIZED or PIPELINE) * @flow: RX flow type (SYNCHRONIZED or PIPELINE)
* @arb_mode: Arbitration mode for TX frame (Round robin, priority) * @arb_mode: Arbitration mode for TX frame (Round robin, priority)
*/ */
struct hsi_config { struct hsi_config {
unsigned int mode; unsigned int mode;
unsigned int channels; struct hsi_channel *channels;
unsigned int num_channels;
unsigned int num_hw_channels;
unsigned int speed; unsigned int speed;
union { union {
unsigned int flow; /* RX only */ unsigned int flow; /* RX only */
...@@ -282,6 +296,21 @@ struct hsi_controller *hsi_alloc_controller(unsigned int n_ports, gfp_t flags); ...@@ -282,6 +296,21 @@ struct hsi_controller *hsi_alloc_controller(unsigned int n_ports, gfp_t flags);
void hsi_put_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);
struct hsi_client *hsi_new_client(struct hsi_port *port,
struct hsi_board_info *info);
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, static inline void hsi_controller_set_drvdata(struct hsi_controller *hsi,
void *data) void *data)
...@@ -305,6 +334,8 @@ static inline struct hsi_port *hsi_find_port_num(struct hsi_controller *hsi, ...@@ -305,6 +334,8 @@ static inline struct hsi_port *hsi_find_port_num(struct hsi_controller *hsi,
*/ */
int hsi_async(struct hsi_client *cl, struct hsi_msg *msg); int hsi_async(struct hsi_client *cl, struct hsi_msg *msg);
int hsi_get_channel_id_by_name(struct hsi_client *cl, char *name);
/** /**
* hsi_id - Get HSI controller ID associated to a client * hsi_id - Get HSI controller ID associated to a client
* @cl: Pointer to a HSI client * @cl: Pointer to a HSI client
......
/*
* ssip_slave.h
*
* SSIP slave support header file
*
* Copyright (C) 2010 Nokia Corporation. All rights reserved.
*
* Contact: Carlos Chinea <carlos.chinea@nokia.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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef __LINUX_SSIP_SLAVE_H__
#define __LINUX_SSIP_SLAVE_H__
#include <linux/hsi/hsi.h>
static inline void ssip_slave_put_master(struct hsi_client *master)
{
}
struct hsi_client *ssip_slave_get_master(struct hsi_client *slave);
int ssip_slave_start_tx(struct hsi_client *master);
int ssip_slave_stop_tx(struct hsi_client *master);
void ssip_reset_event(struct hsi_client *master);
int ssip_slave_running(struct hsi_client *master);
#endif /* __LINUX_SSIP_SLAVE_H__ */
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