18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for s390 eadm subchannels
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright IBM Corp. 2012
68c2ecf20Sopenharmony_ci * Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/kernel_stat.h>
108c2ecf20Sopenharmony_ci#include <linux/completion.h>
118c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
128c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
138c2ecf20Sopenharmony_ci#include <linux/device.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/timer.h>
168c2ecf20Sopenharmony_ci#include <linux/slab.h>
178c2ecf20Sopenharmony_ci#include <linux/list.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <asm/css_chars.h>
208c2ecf20Sopenharmony_ci#include <asm/debug.h>
218c2ecf20Sopenharmony_ci#include <asm/isc.h>
228c2ecf20Sopenharmony_ci#include <asm/cio.h>
238c2ecf20Sopenharmony_ci#include <asm/scsw.h>
248c2ecf20Sopenharmony_ci#include <asm/eadm.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#include "eadm_sch.h"
278c2ecf20Sopenharmony_ci#include "ioasm.h"
288c2ecf20Sopenharmony_ci#include "cio.h"
298c2ecf20Sopenharmony_ci#include "css.h"
308c2ecf20Sopenharmony_ci#include "orb.h"
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("driver for s390 eadm subchannels");
338c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define EADM_TIMEOUT (7 * HZ)
368c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(list_lock);
378c2ecf20Sopenharmony_cistatic LIST_HEAD(eadm_list);
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic debug_info_t *eadm_debug;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define EADM_LOG(imp, txt) do {					\
428c2ecf20Sopenharmony_ci		debug_text_event(eadm_debug, imp, txt);		\
438c2ecf20Sopenharmony_ci	} while (0)
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic void EADM_LOG_HEX(int level, void *data, int length)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	debug_event(eadm_debug, level, data, length);
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic void orb_init(union orb *orb)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	memset(orb, 0, sizeof(union orb));
538c2ecf20Sopenharmony_ci	orb->eadm.compat1 = 1;
548c2ecf20Sopenharmony_ci	orb->eadm.compat2 = 1;
558c2ecf20Sopenharmony_ci	orb->eadm.fmt = 1;
568c2ecf20Sopenharmony_ci	orb->eadm.x = 1;
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic int eadm_subchannel_start(struct subchannel *sch, struct aob *aob)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	union orb *orb = &get_eadm_private(sch)->orb;
628c2ecf20Sopenharmony_ci	int cc;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	orb_init(orb);
658c2ecf20Sopenharmony_ci	orb->eadm.aob = (u32)__pa(aob);
668c2ecf20Sopenharmony_ci	orb->eadm.intparm = (u32)(addr_t)sch;
678c2ecf20Sopenharmony_ci	orb->eadm.key = PAGE_DEFAULT_KEY >> 4;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	EADM_LOG(6, "start");
708c2ecf20Sopenharmony_ci	EADM_LOG_HEX(6, &sch->schid, sizeof(sch->schid));
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	cc = ssch(sch->schid, orb);
738c2ecf20Sopenharmony_ci	switch (cc) {
748c2ecf20Sopenharmony_ci	case 0:
758c2ecf20Sopenharmony_ci		sch->schib.scsw.eadm.actl |= SCSW_ACTL_START_PEND;
768c2ecf20Sopenharmony_ci		break;
778c2ecf20Sopenharmony_ci	case 1:		/* status pending */
788c2ecf20Sopenharmony_ci	case 2:		/* busy */
798c2ecf20Sopenharmony_ci		return -EBUSY;
808c2ecf20Sopenharmony_ci	case 3:		/* not operational */
818c2ecf20Sopenharmony_ci		return -ENODEV;
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci	return 0;
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic int eadm_subchannel_clear(struct subchannel *sch)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	int cc;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	cc = csch(sch->schid);
918c2ecf20Sopenharmony_ci	if (cc)
928c2ecf20Sopenharmony_ci		return -ENODEV;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	sch->schib.scsw.eadm.actl |= SCSW_ACTL_CLEAR_PEND;
958c2ecf20Sopenharmony_ci	return 0;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic void eadm_subchannel_timeout(struct timer_list *t)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	struct eadm_private *private = from_timer(private, t, timer);
1018c2ecf20Sopenharmony_ci	struct subchannel *sch = private->sch;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	spin_lock_irq(sch->lock);
1048c2ecf20Sopenharmony_ci	EADM_LOG(1, "timeout");
1058c2ecf20Sopenharmony_ci	EADM_LOG_HEX(1, &sch->schid, sizeof(sch->schid));
1068c2ecf20Sopenharmony_ci	if (eadm_subchannel_clear(sch))
1078c2ecf20Sopenharmony_ci		EADM_LOG(0, "clear failed");
1088c2ecf20Sopenharmony_ci	spin_unlock_irq(sch->lock);
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic void eadm_subchannel_set_timeout(struct subchannel *sch, int expires)
1128c2ecf20Sopenharmony_ci{
1138c2ecf20Sopenharmony_ci	struct eadm_private *private = get_eadm_private(sch);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (expires == 0) {
1168c2ecf20Sopenharmony_ci		del_timer(&private->timer);
1178c2ecf20Sopenharmony_ci		return;
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci	if (timer_pending(&private->timer)) {
1208c2ecf20Sopenharmony_ci		if (mod_timer(&private->timer, jiffies + expires))
1218c2ecf20Sopenharmony_ci			return;
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci	private->timer.expires = jiffies + expires;
1248c2ecf20Sopenharmony_ci	add_timer(&private->timer);
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic void eadm_subchannel_irq(struct subchannel *sch)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	struct eadm_private *private = get_eadm_private(sch);
1308c2ecf20Sopenharmony_ci	struct eadm_scsw *scsw = &sch->schib.scsw.eadm;
1318c2ecf20Sopenharmony_ci	struct irb *irb = this_cpu_ptr(&cio_irb);
1328c2ecf20Sopenharmony_ci	blk_status_t error = BLK_STS_OK;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	EADM_LOG(6, "irq");
1358c2ecf20Sopenharmony_ci	EADM_LOG_HEX(6, irb, sizeof(*irb));
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	inc_irq_stat(IRQIO_ADM);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	if ((scsw->stctl & (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))
1408c2ecf20Sopenharmony_ci	    && scsw->eswf == 1 && irb->esw.eadm.erw.r)
1418c2ecf20Sopenharmony_ci		error = BLK_STS_IOERR;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	if (scsw->fctl & SCSW_FCTL_CLEAR_FUNC)
1448c2ecf20Sopenharmony_ci		error = BLK_STS_TIMEOUT;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	eadm_subchannel_set_timeout(sch, 0);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	if (private->state != EADM_BUSY) {
1498c2ecf20Sopenharmony_ci		EADM_LOG(1, "irq unsol");
1508c2ecf20Sopenharmony_ci		EADM_LOG_HEX(1, irb, sizeof(*irb));
1518c2ecf20Sopenharmony_ci		private->state = EADM_NOT_OPER;
1528c2ecf20Sopenharmony_ci		css_sched_sch_todo(sch, SCH_TODO_EVAL);
1538c2ecf20Sopenharmony_ci		return;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci	scm_irq_handler((struct aob *)(unsigned long)scsw->aob, error);
1568c2ecf20Sopenharmony_ci	private->state = EADM_IDLE;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	if (private->completion)
1598c2ecf20Sopenharmony_ci		complete(private->completion);
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic struct subchannel *eadm_get_idle_sch(void)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	struct eadm_private *private;
1658c2ecf20Sopenharmony_ci	struct subchannel *sch;
1668c2ecf20Sopenharmony_ci	unsigned long flags;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	spin_lock_irqsave(&list_lock, flags);
1698c2ecf20Sopenharmony_ci	list_for_each_entry(private, &eadm_list, head) {
1708c2ecf20Sopenharmony_ci		sch = private->sch;
1718c2ecf20Sopenharmony_ci		spin_lock(sch->lock);
1728c2ecf20Sopenharmony_ci		if (private->state == EADM_IDLE) {
1738c2ecf20Sopenharmony_ci			private->state = EADM_BUSY;
1748c2ecf20Sopenharmony_ci			list_move_tail(&private->head, &eadm_list);
1758c2ecf20Sopenharmony_ci			spin_unlock(sch->lock);
1768c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&list_lock, flags);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci			return sch;
1798c2ecf20Sopenharmony_ci		}
1808c2ecf20Sopenharmony_ci		spin_unlock(sch->lock);
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&list_lock, flags);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	return NULL;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ciint eadm_start_aob(struct aob *aob)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	struct eadm_private *private;
1908c2ecf20Sopenharmony_ci	struct subchannel *sch;
1918c2ecf20Sopenharmony_ci	unsigned long flags;
1928c2ecf20Sopenharmony_ci	int ret;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	sch = eadm_get_idle_sch();
1958c2ecf20Sopenharmony_ci	if (!sch)
1968c2ecf20Sopenharmony_ci		return -EBUSY;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	spin_lock_irqsave(sch->lock, flags);
1998c2ecf20Sopenharmony_ci	eadm_subchannel_set_timeout(sch, EADM_TIMEOUT);
2008c2ecf20Sopenharmony_ci	ret = eadm_subchannel_start(sch, aob);
2018c2ecf20Sopenharmony_ci	if (!ret)
2028c2ecf20Sopenharmony_ci		goto out_unlock;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/* Handle start subchannel failure. */
2058c2ecf20Sopenharmony_ci	eadm_subchannel_set_timeout(sch, 0);
2068c2ecf20Sopenharmony_ci	private = get_eadm_private(sch);
2078c2ecf20Sopenharmony_ci	private->state = EADM_NOT_OPER;
2088c2ecf20Sopenharmony_ci	css_sched_sch_todo(sch, SCH_TODO_EVAL);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ciout_unlock:
2118c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(sch->lock, flags);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	return ret;
2148c2ecf20Sopenharmony_ci}
2158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(eadm_start_aob);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cistatic int eadm_subchannel_probe(struct subchannel *sch)
2188c2ecf20Sopenharmony_ci{
2198c2ecf20Sopenharmony_ci	struct eadm_private *private;
2208c2ecf20Sopenharmony_ci	int ret;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA);
2238c2ecf20Sopenharmony_ci	if (!private)
2248c2ecf20Sopenharmony_ci		return -ENOMEM;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&private->head);
2278c2ecf20Sopenharmony_ci	timer_setup(&private->timer, eadm_subchannel_timeout, 0);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	spin_lock_irq(sch->lock);
2308c2ecf20Sopenharmony_ci	set_eadm_private(sch, private);
2318c2ecf20Sopenharmony_ci	private->state = EADM_IDLE;
2328c2ecf20Sopenharmony_ci	private->sch = sch;
2338c2ecf20Sopenharmony_ci	sch->isc = EADM_SCH_ISC;
2348c2ecf20Sopenharmony_ci	ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch);
2358c2ecf20Sopenharmony_ci	if (ret) {
2368c2ecf20Sopenharmony_ci		set_eadm_private(sch, NULL);
2378c2ecf20Sopenharmony_ci		spin_unlock_irq(sch->lock);
2388c2ecf20Sopenharmony_ci		kfree(private);
2398c2ecf20Sopenharmony_ci		goto out;
2408c2ecf20Sopenharmony_ci	}
2418c2ecf20Sopenharmony_ci	spin_unlock_irq(sch->lock);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	spin_lock_irq(&list_lock);
2448c2ecf20Sopenharmony_ci	list_add(&private->head, &eadm_list);
2458c2ecf20Sopenharmony_ci	spin_unlock_irq(&list_lock);
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	if (dev_get_uevent_suppress(&sch->dev)) {
2488c2ecf20Sopenharmony_ci		dev_set_uevent_suppress(&sch->dev, 0);
2498c2ecf20Sopenharmony_ci		kobject_uevent(&sch->dev.kobj, KOBJ_ADD);
2508c2ecf20Sopenharmony_ci	}
2518c2ecf20Sopenharmony_ciout:
2528c2ecf20Sopenharmony_ci	return ret;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic void eadm_quiesce(struct subchannel *sch)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	struct eadm_private *private = get_eadm_private(sch);
2588c2ecf20Sopenharmony_ci	DECLARE_COMPLETION_ONSTACK(completion);
2598c2ecf20Sopenharmony_ci	int ret;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	spin_lock_irq(sch->lock);
2628c2ecf20Sopenharmony_ci	if (private->state != EADM_BUSY)
2638c2ecf20Sopenharmony_ci		goto disable;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	if (eadm_subchannel_clear(sch))
2668c2ecf20Sopenharmony_ci		goto disable;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	private->completion = &completion;
2698c2ecf20Sopenharmony_ci	spin_unlock_irq(sch->lock);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	wait_for_completion_io(&completion);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	spin_lock_irq(sch->lock);
2748c2ecf20Sopenharmony_ci	private->completion = NULL;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cidisable:
2778c2ecf20Sopenharmony_ci	eadm_subchannel_set_timeout(sch, 0);
2788c2ecf20Sopenharmony_ci	do {
2798c2ecf20Sopenharmony_ci		ret = cio_disable_subchannel(sch);
2808c2ecf20Sopenharmony_ci	} while (ret == -EBUSY);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	spin_unlock_irq(sch->lock);
2838c2ecf20Sopenharmony_ci}
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_cistatic int eadm_subchannel_remove(struct subchannel *sch)
2868c2ecf20Sopenharmony_ci{
2878c2ecf20Sopenharmony_ci	struct eadm_private *private = get_eadm_private(sch);
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	spin_lock_irq(&list_lock);
2908c2ecf20Sopenharmony_ci	list_del(&private->head);
2918c2ecf20Sopenharmony_ci	spin_unlock_irq(&list_lock);
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	eadm_quiesce(sch);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	spin_lock_irq(sch->lock);
2968c2ecf20Sopenharmony_ci	set_eadm_private(sch, NULL);
2978c2ecf20Sopenharmony_ci	spin_unlock_irq(sch->lock);
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	kfree(private);
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	return 0;
3028c2ecf20Sopenharmony_ci}
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_cistatic void eadm_subchannel_shutdown(struct subchannel *sch)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	eadm_quiesce(sch);
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic int eadm_subchannel_freeze(struct subchannel *sch)
3108c2ecf20Sopenharmony_ci{
3118c2ecf20Sopenharmony_ci	return cio_disable_subchannel(sch);
3128c2ecf20Sopenharmony_ci}
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_cistatic int eadm_subchannel_restore(struct subchannel *sch)
3158c2ecf20Sopenharmony_ci{
3168c2ecf20Sopenharmony_ci	return cio_enable_subchannel(sch, (u32)(unsigned long)sch);
3178c2ecf20Sopenharmony_ci}
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci/**
3208c2ecf20Sopenharmony_ci * eadm_subchannel_sch_event - process subchannel event
3218c2ecf20Sopenharmony_ci * @sch: subchannel
3228c2ecf20Sopenharmony_ci * @process: non-zero if function is called in process context
3238c2ecf20Sopenharmony_ci *
3248c2ecf20Sopenharmony_ci * An unspecified event occurred for this subchannel. Adjust data according
3258c2ecf20Sopenharmony_ci * to the current operational state of the subchannel. Return zero when the
3268c2ecf20Sopenharmony_ci * event has been handled sufficiently or -EAGAIN when this function should
3278c2ecf20Sopenharmony_ci * be called again in process context.
3288c2ecf20Sopenharmony_ci */
3298c2ecf20Sopenharmony_cistatic int eadm_subchannel_sch_event(struct subchannel *sch, int process)
3308c2ecf20Sopenharmony_ci{
3318c2ecf20Sopenharmony_ci	struct eadm_private *private;
3328c2ecf20Sopenharmony_ci	unsigned long flags;
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	spin_lock_irqsave(sch->lock, flags);
3358c2ecf20Sopenharmony_ci	if (!device_is_registered(&sch->dev))
3368c2ecf20Sopenharmony_ci		goto out_unlock;
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	if (work_pending(&sch->todo_work))
3398c2ecf20Sopenharmony_ci		goto out_unlock;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	if (cio_update_schib(sch)) {
3428c2ecf20Sopenharmony_ci		css_sched_sch_todo(sch, SCH_TODO_UNREG);
3438c2ecf20Sopenharmony_ci		goto out_unlock;
3448c2ecf20Sopenharmony_ci	}
3458c2ecf20Sopenharmony_ci	private = get_eadm_private(sch);
3468c2ecf20Sopenharmony_ci	if (private->state == EADM_NOT_OPER)
3478c2ecf20Sopenharmony_ci		private->state = EADM_IDLE;
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ciout_unlock:
3508c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(sch->lock, flags);
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	return 0;
3538c2ecf20Sopenharmony_ci}
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_cistatic struct css_device_id eadm_subchannel_ids[] = {
3568c2ecf20Sopenharmony_ci	{ .match_flags = 0x1, .type = SUBCHANNEL_TYPE_ADM, },
3578c2ecf20Sopenharmony_ci	{ /* end of list */ },
3588c2ecf20Sopenharmony_ci};
3598c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(css, eadm_subchannel_ids);
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_cistatic struct css_driver eadm_subchannel_driver = {
3628c2ecf20Sopenharmony_ci	.drv = {
3638c2ecf20Sopenharmony_ci		.name = "eadm_subchannel",
3648c2ecf20Sopenharmony_ci		.owner = THIS_MODULE,
3658c2ecf20Sopenharmony_ci	},
3668c2ecf20Sopenharmony_ci	.subchannel_type = eadm_subchannel_ids,
3678c2ecf20Sopenharmony_ci	.irq = eadm_subchannel_irq,
3688c2ecf20Sopenharmony_ci	.probe = eadm_subchannel_probe,
3698c2ecf20Sopenharmony_ci	.remove = eadm_subchannel_remove,
3708c2ecf20Sopenharmony_ci	.shutdown = eadm_subchannel_shutdown,
3718c2ecf20Sopenharmony_ci	.sch_event = eadm_subchannel_sch_event,
3728c2ecf20Sopenharmony_ci	.freeze = eadm_subchannel_freeze,
3738c2ecf20Sopenharmony_ci	.thaw = eadm_subchannel_restore,
3748c2ecf20Sopenharmony_ci	.restore = eadm_subchannel_restore,
3758c2ecf20Sopenharmony_ci};
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_cistatic int __init eadm_sch_init(void)
3788c2ecf20Sopenharmony_ci{
3798c2ecf20Sopenharmony_ci	int ret;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	if (!css_general_characteristics.eadm)
3828c2ecf20Sopenharmony_ci		return -ENXIO;
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci	eadm_debug = debug_register("eadm_log", 16, 1, 16);
3858c2ecf20Sopenharmony_ci	if (!eadm_debug)
3868c2ecf20Sopenharmony_ci		return -ENOMEM;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	debug_register_view(eadm_debug, &debug_hex_ascii_view);
3898c2ecf20Sopenharmony_ci	debug_set_level(eadm_debug, 2);
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	isc_register(EADM_SCH_ISC);
3928c2ecf20Sopenharmony_ci	ret = css_driver_register(&eadm_subchannel_driver);
3938c2ecf20Sopenharmony_ci	if (ret)
3948c2ecf20Sopenharmony_ci		goto cleanup;
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	return ret;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_cicleanup:
3998c2ecf20Sopenharmony_ci	isc_unregister(EADM_SCH_ISC);
4008c2ecf20Sopenharmony_ci	debug_unregister(eadm_debug);
4018c2ecf20Sopenharmony_ci	return ret;
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic void __exit eadm_sch_exit(void)
4058c2ecf20Sopenharmony_ci{
4068c2ecf20Sopenharmony_ci	css_driver_unregister(&eadm_subchannel_driver);
4078c2ecf20Sopenharmony_ci	isc_unregister(EADM_SCH_ISC);
4088c2ecf20Sopenharmony_ci	debug_unregister(eadm_debug);
4098c2ecf20Sopenharmony_ci}
4108c2ecf20Sopenharmony_cimodule_init(eadm_sch_init);
4118c2ecf20Sopenharmony_cimodule_exit(eadm_sch_exit);
412