Blame view

drivers/hsi/hsi_core.c 18.4 KB
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  /*
   * HSI core.
   *
   * 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
   */
  #include <linux/hsi/hsi.h>
  #include <linux/compiler.h>
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
24
  #include <linux/list.h>
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
25
26
27
  #include <linux/kobject.h>
  #include <linux/slab.h>
  #include <linux/string.h>
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
28
  #include <linux/notifier.h>
a2aa24734   Sebastian Reichel   HSI: Add common D...
29
30
  #include <linux/of.h>
  #include <linux/of_device.h>
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
31
  #include "hsi_core.h"
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
32
33
34
35
36
37
  static ssize_t modalias_show(struct device *dev,
  			struct device_attribute *a __maybe_unused, char *buf)
  {
  	return sprintf(buf, "hsi:%s
  ", dev_name(dev));
  }
001731468   Greg Kroah-Hartman   hsi: convert bus ...
38
  static DEVICE_ATTR_RO(modalias);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
39

001731468   Greg Kroah-Hartman   hsi: convert bus ...
40
41
42
  static struct attribute *hsi_bus_dev_attrs[] = {
  	&dev_attr_modalias.attr,
  	NULL,
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
43
  };
001731468   Greg Kroah-Hartman   hsi: convert bus ...
44
  ATTRIBUTE_GROUPS(hsi_bus_dev);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
45
46
47
  
  static int hsi_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
  {
6f02b9e9b   Carlos Chinea   HSI: hsi: Remove ...
48
  	add_uevent_var(env, "MODALIAS=hsi:%s", dev_name(dev));
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
49
50
51
52
53
54
  
  	return 0;
  }
  
  static int hsi_bus_match(struct device *dev, struct device_driver *driver)
  {
a2aa24734   Sebastian Reichel   HSI: Add common D...
55
56
57
58
59
60
61
  	if (of_driver_match_device(dev, driver))
  		return true;
  
  	if (strcmp(dev_name(dev), driver->name) == 0)
  		return true;
  
  	return false;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
62
63
64
65
  }
  
  static struct bus_type hsi_bus_type = {
  	.name		= "hsi",
001731468   Greg Kroah-Hartman   hsi: convert bus ...
66
  	.dev_groups	= hsi_bus_dev_groups,
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
67
68
69
70
71
72
  	.match		= hsi_bus_match,
  	.uevent		= hsi_bus_uevent,
  };
  
  static void hsi_client_release(struct device *dev)
  {
a088cf161   Sebastian Reichel   HSI: Add channel ...
73
74
75
76
77
  	struct hsi_client *cl = to_hsi_client(dev);
  
  	kfree(cl->tx_cfg.channels);
  	kfree(cl->rx_cfg.channels);
  	kfree(cl);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
78
  }
849145102   Sebastian Reichel   HSI: export metho...
79
80
  struct hsi_client *hsi_new_client(struct hsi_port *port,
  						struct hsi_board_info *info)
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
81
82
  {
  	struct hsi_client *cl;
a088cf161   Sebastian Reichel   HSI: Add channel ...
83
  	size_t size;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
84
85
86
  
  	cl = kzalloc(sizeof(*cl), GFP_KERNEL);
  	if (!cl)
d2c85ac24   Insu Yun   hsi: correctly ha...
87
  		goto err;
a088cf161   Sebastian Reichel   HSI: Add channel ...
88

a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
89
  	cl->tx_cfg = info->tx_cfg;
a088cf161   Sebastian Reichel   HSI: Add channel ...
90
91
  	if (cl->tx_cfg.channels) {
  		size = cl->tx_cfg.num_channels * sizeof(*cl->tx_cfg.channels);
b32bd7e7d   Muhammad Falak R Wani   hsi: use kmemdup
92
93
  		cl->tx_cfg.channels = kmemdup(info->tx_cfg.channels, size,
  					      GFP_KERNEL);
d2c85ac24   Insu Yun   hsi: correctly ha...
94
95
  		if (!cl->tx_cfg.channels)
  			goto err_tx;
a088cf161   Sebastian Reichel   HSI: Add channel ...
96
  	}
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
97
  	cl->rx_cfg = info->rx_cfg;
a088cf161   Sebastian Reichel   HSI: Add channel ...
98
99
  	if (cl->rx_cfg.channels) {
  		size = cl->rx_cfg.num_channels * sizeof(*cl->rx_cfg.channels);
b32bd7e7d   Muhammad Falak R Wani   hsi: use kmemdup
100
101
  		cl->rx_cfg.channels = kmemdup(info->rx_cfg.channels, size,
  					      GFP_KERNEL);
d2c85ac24   Insu Yun   hsi: correctly ha...
102
103
  		if (!cl->rx_cfg.channels)
  			goto err_rx;
a088cf161   Sebastian Reichel   HSI: Add channel ...
104
  	}
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
105
106
107
  	cl->device.bus = &hsi_bus_type;
  	cl->device.parent = &port->device;
  	cl->device.release = hsi_client_release;
02aa2a376   Kees Cook   drivers: avoid fo...
108
  	dev_set_name(&cl->device, "%s", info->name);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
109
  	cl->device.platform_data = info->platform_data;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
110
111
112
113
114
  	if (info->archdata)
  		cl->device.archdata = *info->archdata;
  	if (device_register(&cl->device) < 0) {
  		pr_err("hsi: failed to register client: %s
  ", info->name);
90e41f9dc   Carlos Chinea   HSI: hsi: Fix err...
115
  		put_device(&cl->device);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
116
  	}
849145102   Sebastian Reichel   HSI: export metho...
117
118
  
  	return cl;
d2c85ac24   Insu Yun   hsi: correctly ha...
119
120
121
122
123
124
  err_rx:
  	kfree(cl->tx_cfg.channels);
  err_tx:
  	kfree(cl);
  err:
  	return NULL;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
125
  }
849145102   Sebastian Reichel   HSI: export metho...
126
  EXPORT_SYMBOL_GPL(hsi_new_client);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
127
128
129
130
131
132
133
134
135
136
137
138
139
140
  
  static void hsi_scan_board_info(struct hsi_controller *hsi)
  {
  	struct hsi_cl_info *cl_info;
  	struct hsi_port	*p;
  
  	list_for_each_entry(cl_info, &hsi_board_list, list)
  		if (cl_info->info.hsi_id == hsi->id) {
  			p = hsi_find_port_num(hsi, cl_info->info.port);
  			if (!p)
  				continue;
  			hsi_new_client(p, &cl_info->info);
  		}
  }
a2aa24734   Sebastian Reichel   HSI: Add common D...
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
  #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
  ", name);
  		put_device(&cl->device);
a2aa24734   Sebastian Reichel   HSI: Add common D...
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
  	}
  
  	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
  ", 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
849145102   Sebastian Reichel   HSI: export metho...
337
  int hsi_remove_client(struct device *dev, void *data __maybe_unused)
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
338
  {
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
339
340
341
342
  	device_unregister(dev);
  
  	return 0;
  }
849145102   Sebastian Reichel   HSI: export metho...
343
  EXPORT_SYMBOL_GPL(hsi_remove_client);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
344
345
346
347
348
349
350
351
  
  static int hsi_remove_port(struct device *dev, void *data __maybe_unused)
  {
  	device_for_each_child(dev, NULL, hsi_remove_client);
  	device_unregister(dev);
  
  	return 0;
  }
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
352
  static void hsi_controller_release(struct device *dev)
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
353
  {
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
354
355
356
357
  	struct hsi_controller *hsi = to_hsi_controller(dev);
  
  	kfree(hsi->port);
  	kfree(hsi);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
358
  }
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
359
  static void hsi_port_release(struct device *dev)
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
360
  {
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
361
  	kfree(to_hsi_port(dev));
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
362
363
364
  }
  
  /**
a0bf37edb   Sebastian Reichel   HSI: method to un...
365
366
367
368
369
370
371
372
373
374
   * 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);
  
  /**
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
   * hsi_unregister_controller - Unregister an HSI controller
   * @hsi: The HSI controller to register
   */
  void hsi_unregister_controller(struct hsi_controller *hsi)
  {
  	device_for_each_child(&hsi->device, NULL, hsi_remove_port);
  	device_unregister(&hsi->device);
  }
  EXPORT_SYMBOL_GPL(hsi_unregister_controller);
  
  /**
   * hsi_register_controller - Register an HSI controller and its ports
   * @hsi: The HSI controller to register
   *
   * Returns -errno on failure, 0 on success.
   */
  int hsi_register_controller(struct hsi_controller *hsi)
  {
  	unsigned int i;
  	int err;
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
395
  	err = device_add(&hsi->device);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
396
397
398
  	if (err < 0)
  		return err;
  	for (i = 0; i < hsi->num_ports; i++) {
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
399
  		hsi->port[i]->device.parent = &hsi->device;
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
400
  		err = device_add(&hsi->port[i]->device);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
401
402
403
404
405
406
407
408
  		if (err < 0)
  			goto out;
  	}
  	/* Populate HSI bus with HSI clients */
  	hsi_scan_board_info(hsi);
  
  	return 0;
  out:
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
409
410
411
  	while (i-- > 0)
  		device_del(&hsi->port[i]->device);
  	device_del(&hsi->device);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
  
  	return err;
  }
  EXPORT_SYMBOL_GPL(hsi_register_controller);
  
  /**
   * hsi_register_client_driver - Register an HSI client to the HSI bus
   * @drv: HSI client driver to register
   *
   * Returns -errno on failure, 0 on success.
   */
  int hsi_register_client_driver(struct hsi_client_driver *drv)
  {
  	drv->driver.bus = &hsi_bus_type;
  
  	return driver_register(&drv->driver);
  }
  EXPORT_SYMBOL_GPL(hsi_register_client_driver);
  
  static inline int hsi_dummy_msg(struct hsi_msg *msg __maybe_unused)
  {
  	return 0;
  }
  
  static inline int hsi_dummy_cl(struct hsi_client *cl __maybe_unused)
  {
  	return 0;
  }
  
  /**
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
   * 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);
  
  /**
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
465
466
467
468
469
470
471
472
473
   * hsi_alloc_controller - Allocate an HSI controller and its ports
   * @n_ports: Number of ports on the HSI controller
   * @flags: Kernel allocation flags
   *
   * Return NULL on failure or a pointer to an hsi_controller on success.
   */
  struct hsi_controller *hsi_alloc_controller(unsigned int n_ports, gfp_t flags)
  {
  	struct hsi_controller	*hsi;
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
474
  	struct hsi_port		**port;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
475
476
477
478
  	unsigned int		i;
  
  	if (!n_ports)
  		return NULL;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
479
480
  	hsi = kzalloc(sizeof(*hsi), flags);
  	if (!hsi)
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
481
482
483
484
485
  		return NULL;
  	port = kzalloc(sizeof(*port)*n_ports, flags);
  	if (!port) {
  		kfree(hsi);
  		return NULL;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
486
487
488
  	}
  	hsi->num_ports = n_ports;
  	hsi->port = port;
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
  	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);
de5a3774d   Sebastian Reichel   HSI: core: switch...
504
  		BLOCKING_INIT_NOTIFIER_HEAD(&port[i]->n_head);
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
505
506
507
508
  		dev_set_name(&port[i]->device, "port%d", i);
  		hsi->port[i]->device.release = hsi_port_release;
  		device_initialize(&hsi->port[i]->device);
  	}
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
509
510
511
  
  	return hsi;
  out:
5a218ceba   Carlos Chinea   HSI: hsi: Rework ...
512
  	hsi_put_controller(hsi);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
513
514
515
516
517
518
  
  	return NULL;
  }
  EXPORT_SYMBOL_GPL(hsi_alloc_controller);
  
  /**
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
   * hsi_free_msg - Free an HSI message
   * @msg: Pointer to the HSI message
   *
   * Client is responsible to free the buffers pointed by the scatterlists.
   */
  void hsi_free_msg(struct hsi_msg *msg)
  {
  	if (!msg)
  		return;
  	sg_free_table(&msg->sgt);
  	kfree(msg);
  }
  EXPORT_SYMBOL_GPL(hsi_free_msg);
  
  /**
   * hsi_alloc_msg - Allocate an HSI message
   * @nents: Number of memory entries
   * @flags: Kernel allocation flags
   *
   * nents can be 0. This mainly makes sense for read transfer.
   * In that case, HSI drivers will call the complete callback when
   * there is data to be read without consuming it.
   *
   * Return NULL on failure or a pointer to an hsi_msg on success.
   */
  struct hsi_msg *hsi_alloc_msg(unsigned int nents, gfp_t flags)
  {
  	struct hsi_msg *msg;
  	int err;
  
  	msg = kzalloc(sizeof(*msg), flags);
  	if (!msg)
  		return NULL;
  
  	if (!nents)
  		return msg;
  
  	err = sg_alloc_table(&msg->sgt, nents, flags);
  	if (unlikely(err)) {
  		kfree(msg);
  		msg = NULL;
  	}
  
  	return msg;
  }
  EXPORT_SYMBOL_GPL(hsi_alloc_msg);
  
  /**
   * hsi_async - Submit an HSI transfer to the controller
   * @cl: HSI client sending the transfer
   * @msg: The HSI transfer passed to controller
   *
   * The HSI message must have the channel, ttype, complete and destructor
   * fields set beforehand. If nents > 0 then the client has to initialize
   * also the scatterlists to point to the buffers to write to or read from.
   *
   * HSI controllers relay on pre-allocated buffers from their clients and they
   * do not allocate buffers on their own.
   *
   * Once the HSI message transfer finishes, the HSI controller calls the
   * complete callback with the status and actual_len fields of the HSI message
   * updated. The complete callback can be called before returning from
   * hsi_async.
   *
   * Returns -errno on failure or 0 on success
   */
  int hsi_async(struct hsi_client *cl, struct hsi_msg *msg)
  {
  	struct hsi_port *port = hsi_get_port(cl);
  
  	if (!hsi_port_claimed(cl))
  		return -EACCES;
  
  	WARN_ON_ONCE(!msg->destructor || !msg->complete);
  	msg->cl = cl;
  
  	return port->async(msg);
  }
  EXPORT_SYMBOL_GPL(hsi_async);
  
  /**
   * hsi_claim_port - Claim the HSI client's port
   * @cl: HSI client that wants to claim its port
   * @share: Flag to indicate if the client wants to share the port or not.
   *
   * Returns -errno on failure, 0 on success.
   */
  int hsi_claim_port(struct hsi_client *cl, unsigned int share)
  {
  	struct hsi_port *port = hsi_get_port(cl);
  	int err = 0;
  
  	mutex_lock(&port->lock);
  	if ((port->claimed) && (!port->shared || !share)) {
  		err = -EBUSY;
  		goto out;
  	}
  	if (!try_module_get(to_hsi_controller(port->device.parent)->owner)) {
  		err = -ENODEV;
  		goto out;
  	}
  	port->claimed++;
  	port->shared = !!share;
  	cl->pclaimed = 1;
  out:
  	mutex_unlock(&port->lock);
  
  	return err;
  }
  EXPORT_SYMBOL_GPL(hsi_claim_port);
  
  /**
   * hsi_release_port - Release the HSI client's port
   * @cl: HSI client which previously claimed its port
   */
  void hsi_release_port(struct hsi_client *cl)
  {
  	struct hsi_port *port = hsi_get_port(cl);
  
  	mutex_lock(&port->lock);
  	/* Allow HW driver to do some cleanup */
  	port->release(cl);
  	if (cl->pclaimed)
  		port->claimed--;
  	BUG_ON(port->claimed < 0);
  	cl->pclaimed = 0;
  	if (!port->claimed)
  		port->shared = 0;
  	module_put(to_hsi_controller(port->device.parent)->owner);
  	mutex_unlock(&port->lock);
  }
  EXPORT_SYMBOL_GPL(hsi_release_port);
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
651
652
  static int hsi_event_notifier_call(struct notifier_block *nb,
  				unsigned long event, void *data __maybe_unused)
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
653
  {
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
654
655
656
  	struct hsi_client *cl = container_of(nb, struct hsi_client, nb);
  
  	(*cl->ehandler)(cl, event);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
657
658
659
  
  	return 0;
  }
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
660
661
662
  /**
   * hsi_register_port_event - Register a client to receive port events
   * @cl: HSI client that wants to receive port events
8eae508b7   Randy Dunlap   hsi: fix kernel-d...
663
   * @handler: Event handler callback
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
664
665
666
667
668
669
670
671
672
673
   *
   * 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))
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
674
  {
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
675
  	struct hsi_port *port = hsi_get_port(cl);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
676

ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
677
678
679
680
681
682
  	if (!handler || cl->ehandler)
  		return -EINVAL;
  	if (!hsi_port_claimed(cl))
  		return -EACCES;
  	cl->ehandler = handler;
  	cl->nb.notifier_call = hsi_event_notifier_call;
de5a3774d   Sebastian Reichel   HSI: core: switch...
683
  	return blocking_notifier_chain_register(&port->n_head, &cl->nb);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
684
  }
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
685
  EXPORT_SYMBOL_GPL(hsi_register_port_event);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
686

ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
687
688
689
690
691
692
693
694
695
696
  /**
   * 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)
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
697
  {
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
698
699
  	struct hsi_port *port = hsi_get_port(cl);
  	int err;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
700

ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
701
  	WARN_ON(!hsi_port_claimed(cl));
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
702

de5a3774d   Sebastian Reichel   HSI: core: switch...
703
  	err = blocking_notifier_chain_unregister(&port->n_head, &cl->nb);
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
704
705
706
707
  	if (!err)
  		cl->ehandler = NULL;
  
  	return err;
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
708
  }
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
709
  EXPORT_SYMBOL_GPL(hsi_unregister_port_event);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
710
711
  
  /**
a2aa24734   Sebastian Reichel   HSI: Add common D...
712
   * hsi_event - Notifies clients about port events
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
713
714
715
716
717
718
719
720
721
722
   * @port: Port where the event occurred
   * @event: The event type
   *
   * Clients should not be concerned about wake line behavior. However, due
   * to a race condition in HSI HW protocol, clients need to be notified
   * about wake line changes, so they can implement a workaround for it.
   *
   * Events:
   * HSI_EVENT_START_RX - Incoming wake line high
   * HSI_EVENT_STOP_RX - Incoming wake line down
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
723
724
   *
   * Returns -errno on error, or 0 on success.
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
725
   */
ec1c56ff8   Carlos Chinea   HSI: hsi: Rework ...
726
  int hsi_event(struct hsi_port *port, unsigned long event)
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
727
  {
de5a3774d   Sebastian Reichel   HSI: core: switch...
728
  	return blocking_notifier_call_chain(&port->n_head, event, NULL);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
729
730
  }
  EXPORT_SYMBOL_GPL(hsi_event);
a088cf161   Sebastian Reichel   HSI: Add channel ...
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
  /**
   * 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);
a056ab8c7   Carlos Chinea   HSI: hsi: Introdu...
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
  static int __init hsi_init(void)
  {
  	return bus_register(&hsi_bus_type);
  }
  postcore_initcall(hsi_init);
  
  static void __exit hsi_exit(void)
  {
  	bus_unregister(&hsi_bus_type);
  }
  module_exit(hsi_exit);
  
  MODULE_AUTHOR("Carlos Chinea <carlos.chinea@nokia.com>");
  MODULE_DESCRIPTION("High-speed Synchronous Serial Interface (HSI) framework");
  MODULE_LICENSE("GPL v2");