aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTony Lindgren <tony@atomide.com>2018-12-16 16:17:56 -0800
committerPavel <pavel@ucw.cz>2019-01-15 12:43:49 +0100
commitc1b14ce8233defdade264608e5bb0b720cd2ed6a (patch)
treec7bd4bbe4a2fa50e50f3f97ff41e0fef07a1fd06
parent48dbfeeff346c9179999173ac5433178d4c11086 (diff)
downloadlinux-k-c1b14ce8233defdade264608e5bb0b720cd2ed6a.tar.gz
tty: n_gsm: Add support for serdev
We can make use of serdev to do simple device drivers for ts27.010 line discipline for things like GNSS, audio mixers and SIM card readers found on many modems. REVISIT: clean-up functions and inline functions for the header Signed-off-by: Tony Lindgren <tony@atomide.com>
-rw-r--r--drivers/tty/n_gsm.c300
-rw-r--r--include/linux/serdev-gsm.h209
2 files changed, 509 insertions, 0 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 8bbb354938ec28..87a46e779ac28e 100644
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -39,6 +39,7 @@
#include <linux/file.h>
#include <linux/uaccess.h>
#include <linux/module.h>
+#include <linux/serdev.h>
#include <linux/timer.h>
#include <linux/tty_flip.h>
#include <linux/tty_driver.h>
@@ -50,6 +51,7 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/gsmmux.h>
+#include <linux/serdev-gsm.h>
static int debug;
module_param(debug, int, 0600);
@@ -147,6 +149,7 @@ struct gsm_dlci {
size_t len);
void (*prev_data)(struct gsm_dlci *dlci, const unsigned char *data,
size_t len);
+ struct gsm_serdev_dlci *ops; /* serdev dlci ops, if used */
struct net_device *net; /* network interface, if created */
};
@@ -181,6 +184,7 @@ struct gsm_control {
*/
struct gsm_mux {
+ struct gsm_serdev *gsd; /* Serial device bus data */
struct tty_struct *tty; /* The tty our ldisc is bound to */
spinlock_t lock;
struct mutex mutex;
@@ -2327,6 +2331,302 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
return 0;
}
+#ifdef CONFIG_SERIAL_DEV_BUS
+static int gsd_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ struct gsm_mux *gsm = gsd->gsm;
+
+ if (!gsm || !c)
+ return -EINVAL;
+
+ gsm_copy_config_values(gsm, c);
+
+ return 0;
+}
+
+static int gsd_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ struct gsm_mux *gsm = gsd->gsm;
+
+ if (!gsm || !c)
+ return -EINVAL;
+
+ return gsm_config(gsm, c);
+}
+
+static struct gsm_dlci *gsd_dlci_get(struct gsm_serdev *gsd, int line,
+ bool allocate)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+
+ if (!gsd || !gsd->gsm || line < 1 || line >= 63)
+ return ERR_PTR(-EINVAL);
+
+ gsm = gsd->gsm;
+
+ mutex_lock(&gsm->mutex);
+
+ if (gsm->dlci[line]) {
+ dlci = gsm->dlci[line];
+ goto unlock;
+ }
+
+ if (!allocate) {
+ dlci = ERR_PTR(-ENODEV);
+ goto unlock;
+ }
+
+ dlci = gsm_dlci_alloc(gsm, line);
+ if (!dlci) {
+ gsm = ERR_PTR(-ENOMEM);
+ goto unlock;
+ }
+
+ gsm->dlci[line] = dlci;
+
+unlock:
+ mutex_unlock(&gsm->mutex);
+
+ return dlci;
+}
+
+static void gsd_dlci_data(struct gsm_dlci *dlci,
+ const unsigned char *buf,
+ size_t len)
+{
+ struct gsm_mux *gsm = dlci->gsm;
+ struct gsm_serdev *gsd = gsm->gsd;
+
+ if (!gsd || !dlci->ops || !dlci->ops->receive_buf)
+ return;
+
+ switch (dlci->adaption) {
+ case 0:
+ case 1:
+ if (dlci->ops->receive_buf)
+ dlci->ops->receive_buf(dlci->ops, buf, len);
+ break;
+ default:
+ pr_warn("dlci%i adaption %i not yet implemented\n",
+ dlci->addr, dlci->adaption);
+ break;
+ }
+}
+
+static size_t gsd_write(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *sd,
+ const unsigned char *buf, size_t len)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+ struct gsm_msg *msg;
+ ssize_t total_size = 0;
+ int h, size;
+ u8 *dp;
+
+ dlci = gsd_dlci_get(gsd, sd->line, false);
+ if (IS_ERR(dlci))
+ return PTR_ERR(dlci);
+
+ gsm = gsd->gsm;
+
+ h = dlci->adaption - 1;
+
+ if (len > gsm->mtu)
+ len = gsm->mtu;
+
+ size = len + h;
+
+ msg = gsm_data_alloc(gsm, dlci->addr, size, gsm->ftype);
+ if (!msg)
+ return -ENOMEM;
+
+ dp = msg->data;
+ switch (dlci->adaption) {
+ case 1:
+ break;
+ case 2:
+ *dp++ = gsm_encode_modem(dlci);
+ break;
+ }
+ memcpy(dp, buf, len);
+ __gsm_data_queue(dlci, msg);
+ total_size += size;
+
+ return total_size;
+}
+
+static void gsd_data_kick(struct gsm_serdev *gsd)
+{
+ struct gsm_mux *gsm = gsd->gsm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gsm->tx_lock, flags);
+ gsm_data_kick(gsm);
+ spin_unlock_irqrestore(&gsm->tx_lock, flags);
+}
+
+static int gsd_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ struct gsm_dlci *dlci;
+ struct gsm_mux *gsm;
+
+ if (!gsd || !ops || !ops->line || !ops->receive_buf)
+ return -EINVAL;
+
+ gsm = gsd->gsm;
+
+ dlci = gsd_dlci_get(gsd, ops->line, true);
+ if (IS_ERR(dlci))
+ return PTR_ERR(dlci);
+
+ if (dlci->state == DLCI_OPENING || dlci->state == DLCI_OPEN ||
+ dlci->state == DLCI_CLOSING)
+ return -EBUSY;
+
+ mutex_lock(&dlci->mutex);
+ dlci->ops = ops;
+ dlci->modem_rx = 0;
+ dlci->prev_data = dlci->data;
+ dlci->data = gsd_dlci_data;
+ mutex_unlock(&dlci->mutex);
+
+ gsm_dlci_begin_open(dlci);
+
+ return 0;
+}
+
+static void gsd_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ struct gsm_mux *gsm;
+ struct gsm_dlci *dlci;
+
+ if (!gsd || !ops || !ops->line)
+ return;
+
+ gsm = gsd->gsm;
+
+ dlci = gsd_dlci_get(gsd, ops->line, false);
+ if (IS_ERR(dlci))
+ return;
+
+ mutex_lock(&dlci->mutex);
+ gsm_destroy_network(dlci);
+ dlci->data = dlci->prev_data;
+ dlci->ops = NULL;
+ mutex_unlock(&dlci->mutex);
+
+ gsm_dlci_begin_close(dlci);
+}
+
+static int gsm_serdev_output(struct gsm_mux *gsm, u8 *data, int len)
+{
+ struct gsm_serdev *gsd = gsm->gsd;
+ struct serdev_device *serdev = gsm->gsd->serdev;
+ bool asleep;
+
+ asleep = atomic_read(&gsd->asleep);
+ if (asleep)
+ return -ENOSPC;
+
+ if (debug & 4)
+ print_hex_dump_bytes("gsm_serdev_output: ",
+ DUMP_PREFIX_OFFSET,
+ data, len);
+ if (gsm->gsd->output)
+ return gsm->gsd->output(gsm->gsd, data, len);
+ else
+ return serdev_device_write_buf(serdev, data, len);
+}
+
+static int gsd_receive_buf(struct serdev_device *serdev, const u8 *data,
+ size_t count)
+{
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+ struct gsm_mux *gsm;
+ const unsigned char *dp;
+ int i;
+
+ if (WARN_ON(!gsd))
+ return 0;
+
+ gsm = gsd->gsm;
+
+ if (debug & 4)
+ print_hex_dump_bytes("gsd_receive_buf: ",
+ DUMP_PREFIX_OFFSET,
+ data, count);
+
+ for (i = count, dp = data; i; i--, dp++)
+ gsm->receive(gsm, *dp);
+
+ return count;
+}
+
+static void gsd_write_wakeup(struct serdev_device *serdev)
+{
+ serdev_device_write_wakeup(serdev);
+}
+
+static struct serdev_device_ops gsd_client_ops = {
+ .receive_buf = gsd_receive_buf,
+ .write_wakeup = gsd_write_wakeup,
+};
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+ struct gsm_mux *gsm;
+ int error;
+
+ if (WARN(!gsd || !gsd->serdev || !gsd->output,
+ "serdev and output must be initialized\n"))
+ return -EINVAL;
+
+ serdev_device_set_client_ops(gsd->serdev, &gsd_client_ops);
+
+ gsm = gsm_alloc_mux();
+ if (!gsm)
+ return -ENOMEM;
+
+ gsm->encoding = 1;
+ gsm->tty = NULL;
+ gsm->gsd = gsd;
+ atomic_set(&gsd->asleep, 0);
+ gsd->gsm = gsm;
+ gsd->get_config = gsd_get_config;
+ gsd->set_config = gsd_set_config;
+ gsd->register_dlci = gsd_register_dlci;
+ gsd->unregister_dlci = gsd_unregister_dlci;
+ gsm->output = gsm_serdev_output;
+ gsd->write = gsd_write;
+ gsd->kick = gsd_data_kick;
+ mux_get(gsd->gsm);
+
+ error = gsm_activate_mux(gsd->gsm);
+ if (error) {
+ gsm_cleanup_mux(gsd->gsm);
+ mux_put(gsd->gsm);
+
+ return error;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_register_device);
+
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd)
+{
+ gsm_cleanup_mux(gsd->gsm);
+ mux_put(gsd->gsm);
+ gsd->gsm = NULL;
+}
+EXPORT_SYMBOL_GPL(gsm_serdev_unregister_device);
+
+#endif /* CONFIG_SERIAL_DEV_BUS */
+
/**
* gsmld_output - write to link
* @gsm: our mux
diff --git a/include/linux/serdev-gsm.h b/include/linux/serdev-gsm.h
new file mode 100644
index 00000000000000..4b5403ab7c821d
--- /dev/null
+++ b/include/linux/serdev-gsm.h
@@ -0,0 +1,209 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+struct serdev_device;
+struct gsm_mux;
+struct gsm_config;
+struct gsm_dlci;
+struct gsm_serdev_dlci;
+
+/**
+ * struct gsm_serdev - serdev-gsm instance
+ * @serdev: serdev instance
+ * @gsm: ts 27.010 n_gsm instance
+ * @asleep: device is in idle state
+ * @drvdata: serdev-gsm consumer driver data
+ * @get_config: get ts 27.010 config
+ * @set_config: set ts 27.010 config
+ * @register_dlci: register ts 27.010 channel
+ * @unregister_dlci: unregister ts 27.010 channel
+ * @output: read data from ts 27.010 channel
+ * @write: write data to a ts 27.010 channel
+ * @kick: indicate more data is ready
+ *
+ * Currently only serdev and output must be initialized, the rest are
+ * are initialized by gsm_serdev_register_dlci().
+ */
+struct gsm_serdev {
+ struct serdev_device *serdev;
+ struct gsm_mux *gsm;
+ atomic_t asleep;
+ void *drvdata;
+ int (*get_config)(struct gsm_serdev *gsd, struct gsm_config *c);
+ int (*set_config)(struct gsm_serdev *gsd, struct gsm_config *c);
+ int (*register_dlci)(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops);
+ void (*unregister_dlci)(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops);
+ int (*output)(struct gsm_serdev *gsd, u8 *data, int len);
+ size_t (*write)(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops,
+ const unsigned char *buf, size_t len);
+ void (*kick)(struct gsm_serdev *gsd);
+};
+
+/**
+ * struct gsm_serdev_dlci - serdev-gsm ts 27.010 channel data
+ * @line: ts 27.010 channel, control channel 0 is not available
+ * @receive_buf: function to handle data received for the channel
+ */
+struct gsm_serdev_dlci {
+ int line;
+ int (*receive_buf)(struct gsm_serdev_dlci *ops,
+ const unsigned char *buf,
+ size_t len);
+};
+
+#ifdef CONFIG_SERIAL_DEV_BUS
+
+int gsm_serdev_register_device(struct gsm_serdev *gsd);
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd);
+
+static inline void *gsm_serdev_get_drvdata(struct device *dev)
+{
+ struct serdev_device *serdev = to_serdev_device(dev);
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+ return gsd->drvdata;
+}
+
+static inline void gsm_serdev_set_drvdata(struct device *dev, void *drvdata)
+{
+ struct serdev_device *serdev = to_serdev_device(dev);
+ struct gsm_serdev *gsd = serdev_device_get_drvdata(serdev);
+
+ gsd->drvdata = drvdata;
+}
+
+/**
+ * gsm_serdev_get_config - read ts 27.010 config
+ * @gsd: serdev-gsm instance
+ * @c: ts 27.010 config data
+ *
+ * See gsm_copy_config_values() for more information.
+ */
+static inline
+int gsm_serdev_get_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ if (!gsd || !gsd->get_config)
+ return -ENODEV;
+
+ return gsd->get_config(gsd, c);
+}
+
+/**
+ * gsm_serdev_set_config - set ts 27.010 config
+ * @gsd: serdev-gsm instance
+ * @c: ts 27.010 config data
+ *
+ * See gsm_config() for more information.
+ */
+static inline
+int gsm_serdev_set_config(struct gsm_serdev *gsd, struct gsm_config *c)
+{
+ if (!gsd || !gsd->serdev || !gsd->set_config)
+ return -ENODEV;
+
+ return gsd->set_config(gsd, c);
+}
+
+/**
+ * gsm_serdev_register_dlci - register a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ */
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ if (!gsd || !gsd->serdev || !gsd->register_dlci)
+ return -ENODEV;
+
+ return gsd->register_dlci(gsd, ops);
+}
+
+/**
+ * gsm_serdev_unregister_dlci - unregister a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ */
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ if (!gsd || !gsd->serdev || !gsd->unregister_dlci)
+ return;
+
+ gsd->unregister_dlci(gsd, ops);
+}
+
+/**
+ * gsm_serdev_write - write data to a ts 27.010 channel
+ * @gsd: serdev-gsm instance
+ * @ops: channel ops
+ * @buf: write buffer
+ * @len: buffer length
+ */
+static inline
+size_t gsm_serdev_write(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops,
+ const unsigned char *buf, size_t len)
+{
+ if (!gsd || !gsd->write)
+ return -ENODEV;
+
+ return gsd->write(gsd, ops, buf, len);
+}
+
+/**
+ * gsm_serdev_data_kick - indicate more data can be trasmitted
+ * @gsd: serdev-gsm instance
+ *
+ * See gsm_data_kick() for more information.
+ */
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+ if (gsd && gsd->kick)
+ gsd->kick(gsd);
+}
+
+#else
+
+static inline
+int gsm_serdev_register_device(struct gsm_serdev *gsd)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_device(struct gsm_serdev *gsd)
+{
+}
+
+static inline
+int gsm_serdev_register_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_unregister_dlci(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops)
+{
+ return -ENODEV;
+}
+
+static inline
+size_t gsm_serdev_write(struct gsm_serdev *gsd,
+ struct gsm_serdev_dlci *ops,
+ const unsigned char *buf, size_t len)
+{
+ return -ENODEV;
+}
+
+static inline
+void gsm_serdev_data_kick(struct gsm_serdev *gsd)
+{
+}
+#endif /* CONFIG_SERIAL_DEV_BUS */