162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2015-2017 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#include <linux/kernel.h>
662306a36Sopenharmony_ci#include <linux/device.h>
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci#include <linux/slab.h>
962306a36Sopenharmony_ci#include <linux/sysfs.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include "siox.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci/*
1462306a36Sopenharmony_ci * The lowest bit in the SIOX status word signals if the in-device watchdog is
1562306a36Sopenharmony_ci * ok. If the bit is set, the device is functional.
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci * On writing the watchdog timer is reset when this bit toggles.
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_ci#define SIOX_STATUS_WDG			0x01
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci * Bits 1 to 3 of the status word read as the bitwise negation of what was
2362306a36Sopenharmony_ci * clocked in before. The value clocked in is changed in each cycle and so
2462306a36Sopenharmony_ci * allows to detect transmit/receive problems.
2562306a36Sopenharmony_ci */
2662306a36Sopenharmony_ci#define SIOX_STATUS_COUNTER		0x0e
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci/*
2962306a36Sopenharmony_ci * Each Siox-Device has a 4 bit type number that is neither 0 nor 15. This is
3062306a36Sopenharmony_ci * available in the upper nibble of the read status.
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * On write these bits are DC.
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_ci#define SIOX_STATUS_TYPE		0xf0
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define CREATE_TRACE_POINTS
3762306a36Sopenharmony_ci#include <trace/events/siox.h>
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic bool siox_is_registered;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic void siox_master_lock(struct siox_master *smaster)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	mutex_lock(&smaster->lock);
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void siox_master_unlock(struct siox_master *smaster)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	mutex_unlock(&smaster->lock);
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic inline u8 siox_status_clean(u8 status_read, u8 status_written)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	/*
5462306a36Sopenharmony_ci	 * bits 3:1 of status sample the respective bit in the status
5562306a36Sopenharmony_ci	 * byte written in the previous cycle but inverted. So if you wrote the
5662306a36Sopenharmony_ci	 * status word as 0xa before (counter = 0b101), it is expected to get
5762306a36Sopenharmony_ci	 * back the counter bits as 0b010.
5862306a36Sopenharmony_ci	 *
5962306a36Sopenharmony_ci	 * So given the last status written this function toggles the there
6062306a36Sopenharmony_ci	 * unset counter bits in the read value such that the counter bits in
6162306a36Sopenharmony_ci	 * the return value are all zero iff the bits were read as expected to
6262306a36Sopenharmony_ci	 * simplify error detection.
6362306a36Sopenharmony_ci	 */
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	return status_read ^ (~status_written & 0xe);
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic bool siox_device_counter_error(struct siox_device *sdevice,
6962306a36Sopenharmony_ci				      u8 status_clean)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	return (status_clean & SIOX_STATUS_COUNTER) != 0;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic bool siox_device_type_error(struct siox_device *sdevice, u8 status_clean)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	u8 statustype = (status_clean & SIOX_STATUS_TYPE) >> 4;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	/*
7962306a36Sopenharmony_ci	 * If the device knows which value the type bits should have, check
8062306a36Sopenharmony_ci	 * against this value otherwise just rule out the invalid values 0b0000
8162306a36Sopenharmony_ci	 * and 0b1111.
8262306a36Sopenharmony_ci	 */
8362306a36Sopenharmony_ci	if (sdevice->statustype) {
8462306a36Sopenharmony_ci		if (statustype != sdevice->statustype)
8562306a36Sopenharmony_ci			return true;
8662306a36Sopenharmony_ci	} else {
8762306a36Sopenharmony_ci		switch (statustype) {
8862306a36Sopenharmony_ci		case 0:
8962306a36Sopenharmony_ci		case 0xf:
9062306a36Sopenharmony_ci			return true;
9162306a36Sopenharmony_ci		}
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return false;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic bool siox_device_wdg_error(struct siox_device *sdevice, u8 status_clean)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	return (status_clean & SIOX_STATUS_WDG) == 0;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/*
10362306a36Sopenharmony_ci * If there is a type or counter error the device is called "unsynced".
10462306a36Sopenharmony_ci */
10562306a36Sopenharmony_cibool siox_device_synced(struct siox_device *sdevice)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	if (siox_device_type_error(sdevice, sdevice->status_read_clean))
10862306a36Sopenharmony_ci		return false;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return !siox_device_counter_error(sdevice, sdevice->status_read_clean);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(siox_device_synced);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/*
11662306a36Sopenharmony_ci * A device is called "connected" if it is synced and the watchdog is not
11762306a36Sopenharmony_ci * asserted.
11862306a36Sopenharmony_ci */
11962306a36Sopenharmony_cibool siox_device_connected(struct siox_device *sdevice)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	if (!siox_device_synced(sdevice))
12262306a36Sopenharmony_ci		return false;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return !siox_device_wdg_error(sdevice, sdevice->status_read_clean);
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(siox_device_connected);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic void siox_poll(struct siox_master *smaster)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	struct siox_device *sdevice;
13162306a36Sopenharmony_ci	size_t i = smaster->setbuf_len;
13262306a36Sopenharmony_ci	unsigned int devno = 0;
13362306a36Sopenharmony_ci	int unsync_error = 0;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	smaster->last_poll = jiffies;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/*
13862306a36Sopenharmony_ci	 * The counter bits change in each second cycle, the watchdog bit
13962306a36Sopenharmony_ci	 * toggles each time.
14062306a36Sopenharmony_ci	 * The counter bits hold values from [0, 6]. 7 would be possible
14162306a36Sopenharmony_ci	 * theoretically but the protocol designer considered that a bad idea
14262306a36Sopenharmony_ci	 * for reasons unknown today. (Maybe that's because then the status read
14362306a36Sopenharmony_ci	 * back has only zeros in the counter bits then which might be confused
14462306a36Sopenharmony_ci	 * with a stuck-at-0 error. But for the same reason (with s/0/1/) 0
14562306a36Sopenharmony_ci	 * could be skipped.)
14662306a36Sopenharmony_ci	 */
14762306a36Sopenharmony_ci	if (++smaster->status > 0x0d)
14862306a36Sopenharmony_ci		smaster->status = 0;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	memset(smaster->buf, 0, smaster->setbuf_len);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	/* prepare data pushed out to devices in buf[0..setbuf_len) */
15362306a36Sopenharmony_ci	list_for_each_entry(sdevice, &smaster->devices, node) {
15462306a36Sopenharmony_ci		struct siox_driver *sdriver =
15562306a36Sopenharmony_ci			to_siox_driver(sdevice->dev.driver);
15662306a36Sopenharmony_ci		sdevice->status_written = smaster->status;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		i -= sdevice->inbytes;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		/*
16162306a36Sopenharmony_ci		 * If the device or a previous one is unsynced, don't pet the
16262306a36Sopenharmony_ci		 * watchdog. This is done to ensure that the device is kept in
16362306a36Sopenharmony_ci		 * reset when something is wrong.
16462306a36Sopenharmony_ci		 */
16562306a36Sopenharmony_ci		if (!siox_device_synced(sdevice))
16662306a36Sopenharmony_ci			unsync_error = 1;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci		if (sdriver && !unsync_error)
16962306a36Sopenharmony_ci			sdriver->set_data(sdevice, sdevice->status_written,
17062306a36Sopenharmony_ci					  &smaster->buf[i + 1]);
17162306a36Sopenharmony_ci		else
17262306a36Sopenharmony_ci			/*
17362306a36Sopenharmony_ci			 * Don't trigger watchdog if there is no driver or a
17462306a36Sopenharmony_ci			 * sync problem
17562306a36Sopenharmony_ci			 */
17662306a36Sopenharmony_ci			sdevice->status_written &= ~SIOX_STATUS_WDG;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		smaster->buf[i] = sdevice->status_written;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci		trace_siox_set_data(smaster, sdevice, devno, i);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci		devno++;
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	smaster->pushpull(smaster, smaster->setbuf_len, smaster->buf,
18662306a36Sopenharmony_ci			  smaster->getbuf_len,
18762306a36Sopenharmony_ci			  smaster->buf + smaster->setbuf_len);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	unsync_error = 0;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	/* interpret data pulled in from devices in buf[setbuf_len..] */
19262306a36Sopenharmony_ci	devno = 0;
19362306a36Sopenharmony_ci	i = smaster->setbuf_len;
19462306a36Sopenharmony_ci	list_for_each_entry(sdevice, &smaster->devices, node) {
19562306a36Sopenharmony_ci		struct siox_driver *sdriver =
19662306a36Sopenharmony_ci			to_siox_driver(sdevice->dev.driver);
19762306a36Sopenharmony_ci		u8 status = smaster->buf[i + sdevice->outbytes - 1];
19862306a36Sopenharmony_ci		u8 status_clean;
19962306a36Sopenharmony_ci		u8 prev_status_clean = sdevice->status_read_clean;
20062306a36Sopenharmony_ci		bool synced = true;
20162306a36Sopenharmony_ci		bool connected = true;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci		if (!siox_device_synced(sdevice))
20462306a36Sopenharmony_ci			unsync_error = 1;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci		/*
20762306a36Sopenharmony_ci		 * If the watchdog bit wasn't toggled in this cycle, report the
20862306a36Sopenharmony_ci		 * watchdog as active to give a consistent view for drivers and
20962306a36Sopenharmony_ci		 * sysfs consumers.
21062306a36Sopenharmony_ci		 */
21162306a36Sopenharmony_ci		if (!sdriver || unsync_error)
21262306a36Sopenharmony_ci			status &= ~SIOX_STATUS_WDG;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		status_clean =
21562306a36Sopenharmony_ci			siox_status_clean(status,
21662306a36Sopenharmony_ci					  sdevice->status_written_lastcycle);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci		/* Check counter and type bits */
21962306a36Sopenharmony_ci		if (siox_device_counter_error(sdevice, status_clean) ||
22062306a36Sopenharmony_ci		    siox_device_type_error(sdevice, status_clean)) {
22162306a36Sopenharmony_ci			bool prev_error;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci			synced = false;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci			/* only report a new error if the last cycle was ok */
22662306a36Sopenharmony_ci			prev_error =
22762306a36Sopenharmony_ci				siox_device_counter_error(sdevice,
22862306a36Sopenharmony_ci							  prev_status_clean) ||
22962306a36Sopenharmony_ci				siox_device_type_error(sdevice,
23062306a36Sopenharmony_ci						       prev_status_clean);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci			if (!prev_error) {
23362306a36Sopenharmony_ci				sdevice->status_errors++;
23462306a36Sopenharmony_ci				sysfs_notify_dirent(sdevice->status_errors_kn);
23562306a36Sopenharmony_ci			}
23662306a36Sopenharmony_ci		}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		/* If the device is unsynced report the watchdog as active */
23962306a36Sopenharmony_ci		if (!synced) {
24062306a36Sopenharmony_ci			status &= ~SIOX_STATUS_WDG;
24162306a36Sopenharmony_ci			status_clean &= ~SIOX_STATUS_WDG;
24262306a36Sopenharmony_ci		}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci		if (siox_device_wdg_error(sdevice, status_clean))
24562306a36Sopenharmony_ci			connected = false;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci		/* The watchdog state changed just now */
24862306a36Sopenharmony_ci		if ((status_clean ^ prev_status_clean) & SIOX_STATUS_WDG) {
24962306a36Sopenharmony_ci			sysfs_notify_dirent(sdevice->watchdog_kn);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci			if (siox_device_wdg_error(sdevice, status_clean)) {
25262306a36Sopenharmony_ci				struct kernfs_node *wd_errs =
25362306a36Sopenharmony_ci					sdevice->watchdog_errors_kn;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci				sdevice->watchdog_errors++;
25662306a36Sopenharmony_ci				sysfs_notify_dirent(wd_errs);
25762306a36Sopenharmony_ci			}
25862306a36Sopenharmony_ci		}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci		if (connected != sdevice->connected)
26162306a36Sopenharmony_ci			sysfs_notify_dirent(sdevice->connected_kn);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci		sdevice->status_read_clean = status_clean;
26462306a36Sopenharmony_ci		sdevice->status_written_lastcycle = sdevice->status_written;
26562306a36Sopenharmony_ci		sdevice->connected = connected;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci		trace_siox_get_data(smaster, sdevice, devno, status_clean, i);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci		/* only give data read to driver if the device is connected */
27062306a36Sopenharmony_ci		if (sdriver && connected)
27162306a36Sopenharmony_ci			sdriver->get_data(sdevice, &smaster->buf[i]);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci		devno++;
27462306a36Sopenharmony_ci		i += sdevice->outbytes;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic int siox_poll_thread(void *data)
27962306a36Sopenharmony_ci{
28062306a36Sopenharmony_ci	struct siox_master *smaster = data;
28162306a36Sopenharmony_ci	signed long timeout = 0;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	get_device(&smaster->dev);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	for (;;) {
28662306a36Sopenharmony_ci		if (kthread_should_stop()) {
28762306a36Sopenharmony_ci			put_device(&smaster->dev);
28862306a36Sopenharmony_ci			return 0;
28962306a36Sopenharmony_ci		}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci		siox_master_lock(smaster);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci		if (smaster->active) {
29462306a36Sopenharmony_ci			unsigned long next_poll =
29562306a36Sopenharmony_ci				smaster->last_poll + smaster->poll_interval;
29662306a36Sopenharmony_ci			if (time_is_before_eq_jiffies(next_poll))
29762306a36Sopenharmony_ci				siox_poll(smaster);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci			timeout = smaster->poll_interval -
30062306a36Sopenharmony_ci				(jiffies - smaster->last_poll);
30162306a36Sopenharmony_ci		} else {
30262306a36Sopenharmony_ci			timeout = MAX_SCHEDULE_TIMEOUT;
30362306a36Sopenharmony_ci		}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci		/*
30662306a36Sopenharmony_ci		 * Set the task to idle while holding the lock. This makes sure
30762306a36Sopenharmony_ci		 * that we don't sleep too long when the bus is reenabled before
30862306a36Sopenharmony_ci		 * schedule_timeout is reached.
30962306a36Sopenharmony_ci		 */
31062306a36Sopenharmony_ci		if (timeout > 0)
31162306a36Sopenharmony_ci			set_current_state(TASK_IDLE);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci		siox_master_unlock(smaster);
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci		if (timeout > 0)
31662306a36Sopenharmony_ci			schedule_timeout(timeout);
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci		/*
31962306a36Sopenharmony_ci		 * I'm not clear if/why it is important to set the state to
32062306a36Sopenharmony_ci		 * RUNNING again, but it fixes a "do not call blocking ops when
32162306a36Sopenharmony_ci		 * !TASK_RUNNING;"-warning.
32262306a36Sopenharmony_ci		 */
32362306a36Sopenharmony_ci		set_current_state(TASK_RUNNING);
32462306a36Sopenharmony_ci	}
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic int __siox_start(struct siox_master *smaster)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	if (!(smaster->setbuf_len + smaster->getbuf_len))
33062306a36Sopenharmony_ci		return -ENODEV;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	if (!smaster->buf)
33362306a36Sopenharmony_ci		return -ENOMEM;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	if (smaster->active)
33662306a36Sopenharmony_ci		return 0;
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	smaster->active = 1;
33962306a36Sopenharmony_ci	wake_up_process(smaster->poll_thread);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	return 1;
34262306a36Sopenharmony_ci}
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_cistatic int siox_start(struct siox_master *smaster)
34562306a36Sopenharmony_ci{
34662306a36Sopenharmony_ci	int ret;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	siox_master_lock(smaster);
34962306a36Sopenharmony_ci	ret = __siox_start(smaster);
35062306a36Sopenharmony_ci	siox_master_unlock(smaster);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	return ret;
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cistatic int __siox_stop(struct siox_master *smaster)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	if (smaster->active) {
35862306a36Sopenharmony_ci		struct siox_device *sdevice;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci		smaster->active = 0;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci		list_for_each_entry(sdevice, &smaster->devices, node) {
36362306a36Sopenharmony_ci			if (sdevice->connected)
36462306a36Sopenharmony_ci				sysfs_notify_dirent(sdevice->connected_kn);
36562306a36Sopenharmony_ci			sdevice->connected = false;
36662306a36Sopenharmony_ci		}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci		return 1;
36962306a36Sopenharmony_ci	}
37062306a36Sopenharmony_ci	return 0;
37162306a36Sopenharmony_ci}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_cistatic int siox_stop(struct siox_master *smaster)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci	int ret;
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	siox_master_lock(smaster);
37862306a36Sopenharmony_ci	ret = __siox_stop(smaster);
37962306a36Sopenharmony_ci	siox_master_unlock(smaster);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	return ret;
38262306a36Sopenharmony_ci}
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cistatic ssize_t type_show(struct device *dev,
38562306a36Sopenharmony_ci			 struct device_attribute *attr, char *buf)
38662306a36Sopenharmony_ci{
38762306a36Sopenharmony_ci	struct siox_device *sdev = to_siox_device(dev);
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	return sprintf(buf, "%s\n", sdev->type);
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(type);
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_cistatic ssize_t inbytes_show(struct device *dev,
39562306a36Sopenharmony_ci			    struct device_attribute *attr, char *buf)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	struct siox_device *sdev = to_siox_device(dev);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	return sprintf(buf, "%zu\n", sdev->inbytes);
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(inbytes);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic ssize_t outbytes_show(struct device *dev,
40562306a36Sopenharmony_ci			     struct device_attribute *attr, char *buf)
40662306a36Sopenharmony_ci{
40762306a36Sopenharmony_ci	struct siox_device *sdev = to_siox_device(dev);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	return sprintf(buf, "%zu\n", sdev->outbytes);
41062306a36Sopenharmony_ci}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(outbytes);
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_cistatic ssize_t status_errors_show(struct device *dev,
41562306a36Sopenharmony_ci				  struct device_attribute *attr, char *buf)
41662306a36Sopenharmony_ci{
41762306a36Sopenharmony_ci	struct siox_device *sdev = to_siox_device(dev);
41862306a36Sopenharmony_ci	unsigned int status_errors;
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	siox_master_lock(sdev->smaster);
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	status_errors = sdev->status_errors;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	siox_master_unlock(sdev->smaster);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	return sprintf(buf, "%u\n", status_errors);
42762306a36Sopenharmony_ci}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_cistatic DEVICE_ATTR_RO(status_errors);
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_cistatic ssize_t connected_show(struct device *dev,
43262306a36Sopenharmony_ci			      struct device_attribute *attr, char *buf)
43362306a36Sopenharmony_ci{
43462306a36Sopenharmony_ci	struct siox_device *sdev = to_siox_device(dev);
43562306a36Sopenharmony_ci	bool connected;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	siox_master_lock(sdev->smaster);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	connected = sdev->connected;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	siox_master_unlock(sdev->smaster);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	return sprintf(buf, "%u\n", connected);
44462306a36Sopenharmony_ci}
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_cistatic DEVICE_ATTR_RO(connected);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_cistatic ssize_t watchdog_show(struct device *dev,
44962306a36Sopenharmony_ci			     struct device_attribute *attr, char *buf)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	struct siox_device *sdev = to_siox_device(dev);
45262306a36Sopenharmony_ci	u8 status;
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	siox_master_lock(sdev->smaster);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	status = sdev->status_read_clean;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	siox_master_unlock(sdev->smaster);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	return sprintf(buf, "%d\n", status & SIOX_STATUS_WDG);
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_cistatic DEVICE_ATTR_RO(watchdog);
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_cistatic ssize_t watchdog_errors_show(struct device *dev,
46662306a36Sopenharmony_ci				    struct device_attribute *attr, char *buf)
46762306a36Sopenharmony_ci{
46862306a36Sopenharmony_ci	struct siox_device *sdev = to_siox_device(dev);
46962306a36Sopenharmony_ci	unsigned int watchdog_errors;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	siox_master_lock(sdev->smaster);
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	watchdog_errors = sdev->watchdog_errors;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	siox_master_unlock(sdev->smaster);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	return sprintf(buf, "%u\n", watchdog_errors);
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(watchdog_errors);
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic struct attribute *siox_device_attrs[] = {
48362306a36Sopenharmony_ci	&dev_attr_type.attr,
48462306a36Sopenharmony_ci	&dev_attr_inbytes.attr,
48562306a36Sopenharmony_ci	&dev_attr_outbytes.attr,
48662306a36Sopenharmony_ci	&dev_attr_status_errors.attr,
48762306a36Sopenharmony_ci	&dev_attr_connected.attr,
48862306a36Sopenharmony_ci	&dev_attr_watchdog.attr,
48962306a36Sopenharmony_ci	&dev_attr_watchdog_errors.attr,
49062306a36Sopenharmony_ci	NULL
49162306a36Sopenharmony_ci};
49262306a36Sopenharmony_ciATTRIBUTE_GROUPS(siox_device);
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_cistatic void siox_device_release(struct device *dev)
49562306a36Sopenharmony_ci{
49662306a36Sopenharmony_ci	struct siox_device *sdevice = to_siox_device(dev);
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	kfree(sdevice);
49962306a36Sopenharmony_ci}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_cistatic struct device_type siox_device_type = {
50262306a36Sopenharmony_ci	.groups = siox_device_groups,
50362306a36Sopenharmony_ci	.release = siox_device_release,
50462306a36Sopenharmony_ci};
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_cistatic int siox_match(struct device *dev, struct device_driver *drv)
50762306a36Sopenharmony_ci{
50862306a36Sopenharmony_ci	if (dev->type != &siox_device_type)
50962306a36Sopenharmony_ci		return 0;
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	/* up to now there is only a single driver so keeping this simple */
51262306a36Sopenharmony_ci	return 1;
51362306a36Sopenharmony_ci}
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_cistatic int siox_probe(struct device *dev)
51662306a36Sopenharmony_ci{
51762306a36Sopenharmony_ci	struct siox_driver *sdriver = to_siox_driver(dev->driver);
51862306a36Sopenharmony_ci	struct siox_device *sdevice = to_siox_device(dev);
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	return sdriver->probe(sdevice);
52162306a36Sopenharmony_ci}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_cistatic void siox_remove(struct device *dev)
52462306a36Sopenharmony_ci{
52562306a36Sopenharmony_ci	struct siox_driver *sdriver =
52662306a36Sopenharmony_ci		container_of(dev->driver, struct siox_driver, driver);
52762306a36Sopenharmony_ci	struct siox_device *sdevice = to_siox_device(dev);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	if (sdriver->remove)
53062306a36Sopenharmony_ci		sdriver->remove(sdevice);
53162306a36Sopenharmony_ci}
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_cistatic void siox_shutdown(struct device *dev)
53462306a36Sopenharmony_ci{
53562306a36Sopenharmony_ci	struct siox_device *sdevice = to_siox_device(dev);
53662306a36Sopenharmony_ci	struct siox_driver *sdriver;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	if (!dev->driver)
53962306a36Sopenharmony_ci		return;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	sdriver = container_of(dev->driver, struct siox_driver, driver);
54262306a36Sopenharmony_ci	if (sdriver->shutdown)
54362306a36Sopenharmony_ci		sdriver->shutdown(sdevice);
54462306a36Sopenharmony_ci}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_cistatic struct bus_type siox_bus_type = {
54762306a36Sopenharmony_ci	.name = "siox",
54862306a36Sopenharmony_ci	.match = siox_match,
54962306a36Sopenharmony_ci	.probe = siox_probe,
55062306a36Sopenharmony_ci	.remove = siox_remove,
55162306a36Sopenharmony_ci	.shutdown = siox_shutdown,
55262306a36Sopenharmony_ci};
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_cistatic ssize_t active_show(struct device *dev,
55562306a36Sopenharmony_ci			   struct device_attribute *attr, char *buf)
55662306a36Sopenharmony_ci{
55762306a36Sopenharmony_ci	struct siox_master *smaster = to_siox_master(dev);
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	return sprintf(buf, "%d\n", smaster->active);
56062306a36Sopenharmony_ci}
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_cistatic ssize_t active_store(struct device *dev,
56362306a36Sopenharmony_ci			    struct device_attribute *attr,
56462306a36Sopenharmony_ci			    const char *buf, size_t count)
56562306a36Sopenharmony_ci{
56662306a36Sopenharmony_ci	struct siox_master *smaster = to_siox_master(dev);
56762306a36Sopenharmony_ci	int ret;
56862306a36Sopenharmony_ci	int active;
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	ret = kstrtoint(buf, 0, &active);
57162306a36Sopenharmony_ci	if (ret < 0)
57262306a36Sopenharmony_ci		return ret;
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	if (active)
57562306a36Sopenharmony_ci		ret = siox_start(smaster);
57662306a36Sopenharmony_ci	else
57762306a36Sopenharmony_ci		ret = siox_stop(smaster);
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	if (ret < 0)
58062306a36Sopenharmony_ci		return ret;
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	return count;
58362306a36Sopenharmony_ci}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_cistatic DEVICE_ATTR_RW(active);
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_cistatic struct siox_device *siox_device_add(struct siox_master *smaster,
58862306a36Sopenharmony_ci					   const char *type, size_t inbytes,
58962306a36Sopenharmony_ci					   size_t outbytes, u8 statustype);
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_cistatic ssize_t device_add_store(struct device *dev,
59262306a36Sopenharmony_ci				struct device_attribute *attr,
59362306a36Sopenharmony_ci				const char *buf, size_t count)
59462306a36Sopenharmony_ci{
59562306a36Sopenharmony_ci	struct siox_master *smaster = to_siox_master(dev);
59662306a36Sopenharmony_ci	int ret;
59762306a36Sopenharmony_ci	char type[20] = "";
59862306a36Sopenharmony_ci	size_t inbytes = 0, outbytes = 0;
59962306a36Sopenharmony_ci	u8 statustype = 0;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	ret = sscanf(buf, "%19s %zu %zu %hhu", type, &inbytes,
60262306a36Sopenharmony_ci		     &outbytes, &statustype);
60362306a36Sopenharmony_ci	if (ret != 3 && ret != 4)
60462306a36Sopenharmony_ci		return -EINVAL;
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci	if (strcmp(type, "siox-12x8") || inbytes != 2 || outbytes != 4)
60762306a36Sopenharmony_ci		return -EINVAL;
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	siox_device_add(smaster, "siox-12x8", inbytes, outbytes, statustype);
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	return count;
61262306a36Sopenharmony_ci}
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_cistatic DEVICE_ATTR_WO(device_add);
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_cistatic void siox_device_remove(struct siox_master *smaster);
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cistatic ssize_t device_remove_store(struct device *dev,
61962306a36Sopenharmony_ci				   struct device_attribute *attr,
62062306a36Sopenharmony_ci				   const char *buf, size_t count)
62162306a36Sopenharmony_ci{
62262306a36Sopenharmony_ci	struct siox_master *smaster = to_siox_master(dev);
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	/* XXX? require to write <type> <inbytes> <outbytes> */
62562306a36Sopenharmony_ci	siox_device_remove(smaster);
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	return count;
62862306a36Sopenharmony_ci}
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_cistatic DEVICE_ATTR_WO(device_remove);
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic ssize_t poll_interval_ns_show(struct device *dev,
63362306a36Sopenharmony_ci				     struct device_attribute *attr, char *buf)
63462306a36Sopenharmony_ci{
63562306a36Sopenharmony_ci	struct siox_master *smaster = to_siox_master(dev);
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	return sprintf(buf, "%lld\n", jiffies_to_nsecs(smaster->poll_interval));
63862306a36Sopenharmony_ci}
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_cistatic ssize_t poll_interval_ns_store(struct device *dev,
64162306a36Sopenharmony_ci				      struct device_attribute *attr,
64262306a36Sopenharmony_ci				      const char *buf, size_t count)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	struct siox_master *smaster = to_siox_master(dev);
64562306a36Sopenharmony_ci	int ret;
64662306a36Sopenharmony_ci	u64 val;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	ret = kstrtou64(buf, 0, &val);
64962306a36Sopenharmony_ci	if (ret < 0)
65062306a36Sopenharmony_ci		return ret;
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	siox_master_lock(smaster);
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	smaster->poll_interval = nsecs_to_jiffies(val);
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	siox_master_unlock(smaster);
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	return count;
65962306a36Sopenharmony_ci}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(poll_interval_ns);
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_cistatic struct attribute *siox_master_attrs[] = {
66462306a36Sopenharmony_ci	&dev_attr_active.attr,
66562306a36Sopenharmony_ci	&dev_attr_device_add.attr,
66662306a36Sopenharmony_ci	&dev_attr_device_remove.attr,
66762306a36Sopenharmony_ci	&dev_attr_poll_interval_ns.attr,
66862306a36Sopenharmony_ci	NULL
66962306a36Sopenharmony_ci};
67062306a36Sopenharmony_ciATTRIBUTE_GROUPS(siox_master);
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_cistatic void siox_master_release(struct device *dev)
67362306a36Sopenharmony_ci{
67462306a36Sopenharmony_ci	struct siox_master *smaster = to_siox_master(dev);
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	kfree(smaster);
67762306a36Sopenharmony_ci}
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_cistatic struct device_type siox_master_type = {
68062306a36Sopenharmony_ci	.groups = siox_master_groups,
68162306a36Sopenharmony_ci	.release = siox_master_release,
68262306a36Sopenharmony_ci};
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_cistruct siox_master *siox_master_alloc(struct device *dev,
68562306a36Sopenharmony_ci				      size_t size)
68662306a36Sopenharmony_ci{
68762306a36Sopenharmony_ci	struct siox_master *smaster;
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	if (!dev)
69062306a36Sopenharmony_ci		return NULL;
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci	smaster = kzalloc(sizeof(*smaster) + size, GFP_KERNEL);
69362306a36Sopenharmony_ci	if (!smaster)
69462306a36Sopenharmony_ci		return NULL;
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci	device_initialize(&smaster->dev);
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci	smaster->busno = -1;
69962306a36Sopenharmony_ci	smaster->dev.bus = &siox_bus_type;
70062306a36Sopenharmony_ci	smaster->dev.type = &siox_master_type;
70162306a36Sopenharmony_ci	smaster->dev.parent = dev;
70262306a36Sopenharmony_ci	smaster->poll_interval = DIV_ROUND_UP(HZ, 40);
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci	dev_set_drvdata(&smaster->dev, &smaster[1]);
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	return smaster;
70762306a36Sopenharmony_ci}
70862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(siox_master_alloc);
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ciint siox_master_register(struct siox_master *smaster)
71162306a36Sopenharmony_ci{
71262306a36Sopenharmony_ci	int ret;
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	if (!siox_is_registered)
71562306a36Sopenharmony_ci		return -EPROBE_DEFER;
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	if (!smaster->pushpull)
71862306a36Sopenharmony_ci		return -EINVAL;
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci	dev_set_name(&smaster->dev, "siox-%d", smaster->busno);
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci	mutex_init(&smaster->lock);
72362306a36Sopenharmony_ci	INIT_LIST_HEAD(&smaster->devices);
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	smaster->last_poll = jiffies;
72662306a36Sopenharmony_ci	smaster->poll_thread = kthread_run(siox_poll_thread, smaster,
72762306a36Sopenharmony_ci					   "siox-%d", smaster->busno);
72862306a36Sopenharmony_ci	if (IS_ERR(smaster->poll_thread)) {
72962306a36Sopenharmony_ci		smaster->active = 0;
73062306a36Sopenharmony_ci		return PTR_ERR(smaster->poll_thread);
73162306a36Sopenharmony_ci	}
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	ret = device_add(&smaster->dev);
73462306a36Sopenharmony_ci	if (ret)
73562306a36Sopenharmony_ci		kthread_stop(smaster->poll_thread);
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_ci	return ret;
73862306a36Sopenharmony_ci}
73962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(siox_master_register);
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_civoid siox_master_unregister(struct siox_master *smaster)
74262306a36Sopenharmony_ci{
74362306a36Sopenharmony_ci	/* remove device */
74462306a36Sopenharmony_ci	device_del(&smaster->dev);
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_ci	siox_master_lock(smaster);
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_ci	__siox_stop(smaster);
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ci	while (smaster->num_devices) {
75162306a36Sopenharmony_ci		struct siox_device *sdevice;
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci		sdevice = container_of(smaster->devices.prev,
75462306a36Sopenharmony_ci				       struct siox_device, node);
75562306a36Sopenharmony_ci		list_del(&sdevice->node);
75662306a36Sopenharmony_ci		smaster->num_devices--;
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci		siox_master_unlock(smaster);
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci		device_unregister(&sdevice->dev);
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci		siox_master_lock(smaster);
76362306a36Sopenharmony_ci	}
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	siox_master_unlock(smaster);
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	put_device(&smaster->dev);
76862306a36Sopenharmony_ci}
76962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(siox_master_unregister);
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_cistatic struct siox_device *siox_device_add(struct siox_master *smaster,
77262306a36Sopenharmony_ci					   const char *type, size_t inbytes,
77362306a36Sopenharmony_ci					   size_t outbytes, u8 statustype)
77462306a36Sopenharmony_ci{
77562306a36Sopenharmony_ci	struct siox_device *sdevice;
77662306a36Sopenharmony_ci	int ret;
77762306a36Sopenharmony_ci	size_t buf_len;
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	sdevice = kzalloc(sizeof(*sdevice), GFP_KERNEL);
78062306a36Sopenharmony_ci	if (!sdevice)
78162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	sdevice->type = type;
78462306a36Sopenharmony_ci	sdevice->inbytes = inbytes;
78562306a36Sopenharmony_ci	sdevice->outbytes = outbytes;
78662306a36Sopenharmony_ci	sdevice->statustype = statustype;
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	sdevice->smaster = smaster;
78962306a36Sopenharmony_ci	sdevice->dev.parent = &smaster->dev;
79062306a36Sopenharmony_ci	sdevice->dev.bus = &siox_bus_type;
79162306a36Sopenharmony_ci	sdevice->dev.type = &siox_device_type;
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	siox_master_lock(smaster);
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	dev_set_name(&sdevice->dev, "siox-%d-%d",
79662306a36Sopenharmony_ci		     smaster->busno, smaster->num_devices);
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	buf_len = smaster->setbuf_len + inbytes +
79962306a36Sopenharmony_ci		smaster->getbuf_len + outbytes;
80062306a36Sopenharmony_ci	if (smaster->buf_len < buf_len) {
80162306a36Sopenharmony_ci		u8 *buf = krealloc(smaster->buf, buf_len, GFP_KERNEL);
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci		if (!buf) {
80462306a36Sopenharmony_ci			dev_err(&smaster->dev,
80562306a36Sopenharmony_ci				"failed to realloc buffer to %zu\n", buf_len);
80662306a36Sopenharmony_ci			ret = -ENOMEM;
80762306a36Sopenharmony_ci			goto err_buf_alloc;
80862306a36Sopenharmony_ci		}
80962306a36Sopenharmony_ci
81062306a36Sopenharmony_ci		smaster->buf_len = buf_len;
81162306a36Sopenharmony_ci		smaster->buf = buf;
81262306a36Sopenharmony_ci	}
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_ci	ret = device_register(&sdevice->dev);
81562306a36Sopenharmony_ci	if (ret) {
81662306a36Sopenharmony_ci		dev_err(&smaster->dev, "failed to register device: %d\n", ret);
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci		goto err_device_register;
81962306a36Sopenharmony_ci	}
82062306a36Sopenharmony_ci
82162306a36Sopenharmony_ci	smaster->num_devices++;
82262306a36Sopenharmony_ci	list_add_tail(&sdevice->node, &smaster->devices);
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_ci	smaster->setbuf_len += sdevice->inbytes;
82562306a36Sopenharmony_ci	smaster->getbuf_len += sdevice->outbytes;
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	sdevice->status_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd,
82862306a36Sopenharmony_ci						     "status_errors");
82962306a36Sopenharmony_ci	sdevice->watchdog_kn = sysfs_get_dirent(sdevice->dev.kobj.sd,
83062306a36Sopenharmony_ci						"watchdog");
83162306a36Sopenharmony_ci	sdevice->watchdog_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd,
83262306a36Sopenharmony_ci						       "watchdog_errors");
83362306a36Sopenharmony_ci	sdevice->connected_kn = sysfs_get_dirent(sdevice->dev.kobj.sd,
83462306a36Sopenharmony_ci						 "connected");
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	siox_master_unlock(smaster);
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci	return sdevice;
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_cierr_device_register:
84162306a36Sopenharmony_ci	/* don't care to make the buffer smaller again */
84262306a36Sopenharmony_ci	put_device(&sdevice->dev);
84362306a36Sopenharmony_ci	sdevice = NULL;
84462306a36Sopenharmony_ci
84562306a36Sopenharmony_cierr_buf_alloc:
84662306a36Sopenharmony_ci	siox_master_unlock(smaster);
84762306a36Sopenharmony_ci
84862306a36Sopenharmony_ci	kfree(sdevice);
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ci	return ERR_PTR(ret);
85162306a36Sopenharmony_ci}
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_cistatic void siox_device_remove(struct siox_master *smaster)
85462306a36Sopenharmony_ci{
85562306a36Sopenharmony_ci	struct siox_device *sdevice;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	siox_master_lock(smaster);
85862306a36Sopenharmony_ci
85962306a36Sopenharmony_ci	if (!smaster->num_devices) {
86062306a36Sopenharmony_ci		siox_master_unlock(smaster);
86162306a36Sopenharmony_ci		return;
86262306a36Sopenharmony_ci	}
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_ci	sdevice = container_of(smaster->devices.prev, struct siox_device, node);
86562306a36Sopenharmony_ci	list_del(&sdevice->node);
86662306a36Sopenharmony_ci	smaster->num_devices--;
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	smaster->setbuf_len -= sdevice->inbytes;
86962306a36Sopenharmony_ci	smaster->getbuf_len -= sdevice->outbytes;
87062306a36Sopenharmony_ci
87162306a36Sopenharmony_ci	if (!smaster->num_devices)
87262306a36Sopenharmony_ci		__siox_stop(smaster);
87362306a36Sopenharmony_ci
87462306a36Sopenharmony_ci	siox_master_unlock(smaster);
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_ci	/*
87762306a36Sopenharmony_ci	 * This must be done without holding the master lock because we're
87862306a36Sopenharmony_ci	 * called from device_remove_store which also holds a sysfs mutex.
87962306a36Sopenharmony_ci	 * device_unregister tries to aquire the same lock.
88062306a36Sopenharmony_ci	 */
88162306a36Sopenharmony_ci	device_unregister(&sdevice->dev);
88262306a36Sopenharmony_ci}
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_ciint __siox_driver_register(struct siox_driver *sdriver, struct module *owner)
88562306a36Sopenharmony_ci{
88662306a36Sopenharmony_ci	int ret;
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_ci	if (unlikely(!siox_is_registered))
88962306a36Sopenharmony_ci		return -EPROBE_DEFER;
89062306a36Sopenharmony_ci
89162306a36Sopenharmony_ci	if (!sdriver->probe ||
89262306a36Sopenharmony_ci	    (!sdriver->set_data && !sdriver->get_data)) {
89362306a36Sopenharmony_ci		pr_err("Driver %s doesn't provide needed callbacks\n",
89462306a36Sopenharmony_ci		       sdriver->driver.name);
89562306a36Sopenharmony_ci		return -EINVAL;
89662306a36Sopenharmony_ci	}
89762306a36Sopenharmony_ci
89862306a36Sopenharmony_ci	sdriver->driver.owner = owner;
89962306a36Sopenharmony_ci	sdriver->driver.bus = &siox_bus_type;
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	ret = driver_register(&sdriver->driver);
90262306a36Sopenharmony_ci	if (ret)
90362306a36Sopenharmony_ci		pr_err("Failed to register siox driver %s (%d)\n",
90462306a36Sopenharmony_ci		       sdriver->driver.name, ret);
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_ci	return ret;
90762306a36Sopenharmony_ci}
90862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__siox_driver_register);
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_cistatic int __init siox_init(void)
91162306a36Sopenharmony_ci{
91262306a36Sopenharmony_ci	int ret;
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci	ret = bus_register(&siox_bus_type);
91562306a36Sopenharmony_ci	if (ret) {
91662306a36Sopenharmony_ci		pr_err("Registration of SIOX bus type failed: %d\n", ret);
91762306a36Sopenharmony_ci		return ret;
91862306a36Sopenharmony_ci	}
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci	siox_is_registered = true;
92162306a36Sopenharmony_ci
92262306a36Sopenharmony_ci	return 0;
92362306a36Sopenharmony_ci}
92462306a36Sopenharmony_cisubsys_initcall(siox_init);
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_cistatic void __exit siox_exit(void)
92762306a36Sopenharmony_ci{
92862306a36Sopenharmony_ci	bus_unregister(&siox_bus_type);
92962306a36Sopenharmony_ci}
93062306a36Sopenharmony_cimodule_exit(siox_exit);
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ciMODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
93362306a36Sopenharmony_ciMODULE_DESCRIPTION("Eckelmann SIOX driver core");
93462306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
935