18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * I/O Processor (IOP) management
38c2ecf20Sopenharmony_ci * Written and (C) 1999 by Joshua M. Thompson (funaho@jurai.org)
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Redistribution and use in source and binary forms, with or without
68c2ecf20Sopenharmony_ci * modification, are permitted provided that the following conditions
78c2ecf20Sopenharmony_ci * are met:
88c2ecf20Sopenharmony_ci * 1. Redistributions of source code must retain the above copyright
98c2ecf20Sopenharmony_ci *    notice and this list of conditions.
108c2ecf20Sopenharmony_ci * 2. Redistributions in binary form must reproduce the above copyright
118c2ecf20Sopenharmony_ci *    notice and this list of conditions in the documentation and/or other
128c2ecf20Sopenharmony_ci *    materials provided with the distribution.
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/*
168c2ecf20Sopenharmony_ci * The IOP chips are used in the IIfx and some Quadras (900, 950) to manage
178c2ecf20Sopenharmony_ci * serial and ADB. They are actually a 6502 processor and some glue logic.
188c2ecf20Sopenharmony_ci *
198c2ecf20Sopenharmony_ci * 990429 (jmt) - Initial implementation, just enough to knock the SCC IOP
208c2ecf20Sopenharmony_ci *		  into compatible mode so nobody has to fiddle with the
218c2ecf20Sopenharmony_ci *		  Serial Switch control panel anymore.
228c2ecf20Sopenharmony_ci * 990603 (jmt) - Added code to grab the correct ISM IOP interrupt for OSS
238c2ecf20Sopenharmony_ci *		  and non-OSS machines (at least I hope it's correct on a
248c2ecf20Sopenharmony_ci *		  non-OSS machine -- someone with a Q900 or Q950 needs to
258c2ecf20Sopenharmony_ci *		  check this.)
268c2ecf20Sopenharmony_ci * 990605 (jmt) - Rearranged things a bit wrt IOP detection; iop_present is
278c2ecf20Sopenharmony_ci *		  gone, IOP base addresses are now in an array and the
288c2ecf20Sopenharmony_ci *		  globally-visible functions take an IOP number instead of an
298c2ecf20Sopenharmony_ci *		  an actual base address.
308c2ecf20Sopenharmony_ci * 990610 (jmt) - Finished the message passing framework and it seems to work.
318c2ecf20Sopenharmony_ci *		  Sending _definitely_ works; my adb-bus.c mods can send
328c2ecf20Sopenharmony_ci *		  messages and receive the MSG_COMPLETED status back from the
338c2ecf20Sopenharmony_ci *		  IOP. The trick now is figuring out the message formats.
348c2ecf20Sopenharmony_ci * 990611 (jmt) - More cleanups. Fixed problem where unclaimed messages on a
358c2ecf20Sopenharmony_ci *		  receive channel were never properly acknowledged. Bracketed
368c2ecf20Sopenharmony_ci *		  the remaining debug printk's with #ifdef's and disabled
378c2ecf20Sopenharmony_ci *		  debugging. I can now type on the console.
388c2ecf20Sopenharmony_ci * 990612 (jmt) - Copyright notice added. Reworked the way replies are handled.
398c2ecf20Sopenharmony_ci *		  It turns out that replies are placed back in the send buffer
408c2ecf20Sopenharmony_ci *		  for that channel; messages on the receive channels are always
418c2ecf20Sopenharmony_ci *		  unsolicited messages from the IOP (and our replies to them
428c2ecf20Sopenharmony_ci *		  should go back in the receive channel.) Also added tracking
438c2ecf20Sopenharmony_ci *		  of device names to the listener functions ala the interrupt
448c2ecf20Sopenharmony_ci *		  handlers.
458c2ecf20Sopenharmony_ci * 990729 (jmt) - Added passing of pt_regs structure to IOP handlers. This is
468c2ecf20Sopenharmony_ci *		  used by the new unified ADB driver.
478c2ecf20Sopenharmony_ci *
488c2ecf20Sopenharmony_ci * TODO:
498c2ecf20Sopenharmony_ci *
508c2ecf20Sopenharmony_ci * o Something should be periodically checking iop_alive() to make sure the
518c2ecf20Sopenharmony_ci *   IOP hasn't died.
528c2ecf20Sopenharmony_ci * o Some of the IOP manager routines need better error checking and
538c2ecf20Sopenharmony_ci *   return codes. Nothing major, just prettying up.
548c2ecf20Sopenharmony_ci */
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci/*
578c2ecf20Sopenharmony_ci * -----------------------
588c2ecf20Sopenharmony_ci * IOP Message Passing 101
598c2ecf20Sopenharmony_ci * -----------------------
608c2ecf20Sopenharmony_ci *
618c2ecf20Sopenharmony_ci * The host talks to the IOPs using a rather simple message-passing scheme via
628c2ecf20Sopenharmony_ci * a shared memory area in the IOP RAM. Each IOP has seven "channels"; each
638c2ecf20Sopenharmony_ci * channel is connected to a specific software driver on the IOP. For example
648c2ecf20Sopenharmony_ci * on the SCC IOP there is one channel for each serial port. Each channel has
658c2ecf20Sopenharmony_ci * an incoming and and outgoing message queue with a depth of one.
668c2ecf20Sopenharmony_ci *
678c2ecf20Sopenharmony_ci * A message is 32 bytes plus a state byte for the channel (MSG_IDLE, MSG_NEW,
688c2ecf20Sopenharmony_ci * MSG_RCVD, MSG_COMPLETE). To send a message you copy the message into the
698c2ecf20Sopenharmony_ci * buffer, set the state to MSG_NEW and signal the IOP by setting the IRQ flag
708c2ecf20Sopenharmony_ci * in the IOP control to 1. The IOP will move the state to MSG_RCVD when it
718c2ecf20Sopenharmony_ci * receives the message and then to MSG_COMPLETE when the message processing
728c2ecf20Sopenharmony_ci * has completed. It is the host's responsibility at that point to read the
738c2ecf20Sopenharmony_ci * reply back out of the send channel buffer and reset the channel state back
748c2ecf20Sopenharmony_ci * to MSG_IDLE.
758c2ecf20Sopenharmony_ci *
768c2ecf20Sopenharmony_ci * To receive message from the IOP the same procedure is used except the roles
778c2ecf20Sopenharmony_ci * are reversed. That is, the IOP puts message in the channel with a state of
788c2ecf20Sopenharmony_ci * MSG_NEW, and the host receives the message and move its state to MSG_RCVD
798c2ecf20Sopenharmony_ci * and then to MSG_COMPLETE when processing is completed and the reply (if any)
808c2ecf20Sopenharmony_ci * has been placed back in the receive channel. The IOP will then reset the
818c2ecf20Sopenharmony_ci * channel state to MSG_IDLE.
828c2ecf20Sopenharmony_ci *
838c2ecf20Sopenharmony_ci * Two sets of host interrupts are provided, INT0 and INT1. Both appear on one
848c2ecf20Sopenharmony_ci * interrupt level; they are distinguished by a pair of bits in the IOP status
858c2ecf20Sopenharmony_ci * register. The IOP will raise INT0 when one or more messages in the send
868c2ecf20Sopenharmony_ci * channels have gone to the MSG_COMPLETE state and it will raise INT1 when one
878c2ecf20Sopenharmony_ci * or more messages on the receive channels have gone to the MSG_NEW state.
888c2ecf20Sopenharmony_ci *
898c2ecf20Sopenharmony_ci * Since each channel handles only one message we have to implement a small
908c2ecf20Sopenharmony_ci * interrupt-driven queue on our end. Messages to be sent are placed on the
918c2ecf20Sopenharmony_ci * queue for sending and contain a pointer to an optional callback function.
928c2ecf20Sopenharmony_ci * The handler for a message is called when the message state goes to
938c2ecf20Sopenharmony_ci * MSG_COMPLETE.
948c2ecf20Sopenharmony_ci *
958c2ecf20Sopenharmony_ci * For receiving message we maintain a list of handler functions to call when
968c2ecf20Sopenharmony_ci * a message is received on that IOP/channel combination. The handlers are
978c2ecf20Sopenharmony_ci * called much like an interrupt handler and are passed a copy of the message
988c2ecf20Sopenharmony_ci * from the IOP. The message state will be in MSG_RCVD while the handler runs;
998c2ecf20Sopenharmony_ci * it is the handler's responsibility to call iop_complete_message() when
1008c2ecf20Sopenharmony_ci * finished; this function moves the message state to MSG_COMPLETE and signals
1018c2ecf20Sopenharmony_ci * the IOP. This two-step process is provided to allow the handler to defer
1028c2ecf20Sopenharmony_ci * message processing to a bottom-half handler if the processing will take
1038c2ecf20Sopenharmony_ci * a significant amount of time (handlers are called at interrupt time so they
1048c2ecf20Sopenharmony_ci * should execute quickly.)
1058c2ecf20Sopenharmony_ci */
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci#include <linux/types.h>
1088c2ecf20Sopenharmony_ci#include <linux/kernel.h>
1098c2ecf20Sopenharmony_ci#include <linux/mm.h>
1108c2ecf20Sopenharmony_ci#include <linux/delay.h>
1118c2ecf20Sopenharmony_ci#include <linux/init.h>
1128c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci#include <asm/macintosh.h>
1158c2ecf20Sopenharmony_ci#include <asm/macints.h>
1168c2ecf20Sopenharmony_ci#include <asm/mac_iop.h>
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci#ifdef DEBUG
1198c2ecf20Sopenharmony_ci#define iop_pr_debug(fmt, ...) \
1208c2ecf20Sopenharmony_ci	printk(KERN_DEBUG "%s: " fmt, __func__, ##__VA_ARGS__)
1218c2ecf20Sopenharmony_ci#define iop_pr_cont(fmt, ...) \
1228c2ecf20Sopenharmony_ci	printk(KERN_CONT fmt, ##__VA_ARGS__)
1238c2ecf20Sopenharmony_ci#else
1248c2ecf20Sopenharmony_ci#define iop_pr_debug(fmt, ...) \
1258c2ecf20Sopenharmony_ci	no_printk(KERN_DEBUG "%s: " fmt, __func__, ##__VA_ARGS__)
1268c2ecf20Sopenharmony_ci#define iop_pr_cont(fmt, ...) \
1278c2ecf20Sopenharmony_ci	no_printk(KERN_CONT fmt, ##__VA_ARGS__)
1288c2ecf20Sopenharmony_ci#endif
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci/* Non-zero if the IOPs are present */
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ciint iop_scc_present, iop_ism_present;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci/* structure for tracking channel listeners */
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cistruct listener {
1378c2ecf20Sopenharmony_ci	const char *devname;
1388c2ecf20Sopenharmony_ci	void (*handler)(struct iop_msg *);
1398c2ecf20Sopenharmony_ci};
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci/*
1428c2ecf20Sopenharmony_ci * IOP structures for the two IOPs
1438c2ecf20Sopenharmony_ci *
1448c2ecf20Sopenharmony_ci * The SCC IOP controls both serial ports (A and B) as its two functions.
1458c2ecf20Sopenharmony_ci * The ISM IOP controls the SWIM (floppy drive) and ADB.
1468c2ecf20Sopenharmony_ci */
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic volatile struct mac_iop *iop_base[NUM_IOPS];
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci/*
1518c2ecf20Sopenharmony_ci * IOP message queues
1528c2ecf20Sopenharmony_ci */
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic struct iop_msg iop_msg_pool[NUM_IOP_MSGS];
1558c2ecf20Sopenharmony_cistatic struct iop_msg *iop_send_queue[NUM_IOPS][NUM_IOP_CHAN];
1568c2ecf20Sopenharmony_cistatic struct listener iop_listeners[NUM_IOPS][NUM_IOP_CHAN];
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ciirqreturn_t iop_ism_irq(int, void *);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci/*
1618c2ecf20Sopenharmony_ci * Private access functions
1628c2ecf20Sopenharmony_ci */
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic __inline__ void iop_loadaddr(volatile struct mac_iop *iop, __u16 addr)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	iop->ram_addr_lo = addr;
1678c2ecf20Sopenharmony_ci	iop->ram_addr_hi = addr >> 8;
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic __inline__ __u8 iop_readb(volatile struct mac_iop *iop, __u16 addr)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	iop->ram_addr_lo = addr;
1738c2ecf20Sopenharmony_ci	iop->ram_addr_hi = addr >> 8;
1748c2ecf20Sopenharmony_ci	return iop->ram_data;
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistatic __inline__ void iop_writeb(volatile struct mac_iop *iop, __u16 addr, __u8 data)
1788c2ecf20Sopenharmony_ci{
1798c2ecf20Sopenharmony_ci	iop->ram_addr_lo = addr;
1808c2ecf20Sopenharmony_ci	iop->ram_addr_hi = addr >> 8;
1818c2ecf20Sopenharmony_ci	iop->ram_data = data;
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic __inline__ void iop_stop(volatile struct mac_iop *iop)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	iop->status_ctrl = IOP_AUTOINC;
1878c2ecf20Sopenharmony_ci}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cistatic __inline__ void iop_start(volatile struct mac_iop *iop)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	iop->status_ctrl = IOP_RUN | IOP_AUTOINC;
1928c2ecf20Sopenharmony_ci}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cistatic __inline__ void iop_interrupt(volatile struct mac_iop *iop)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	iop->status_ctrl = IOP_IRQ | IOP_RUN | IOP_AUTOINC;
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic int iop_alive(volatile struct mac_iop *iop)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	int retval;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	retval = (iop_readb(iop, IOP_ADDR_ALIVE) == 0xFF);
2048c2ecf20Sopenharmony_ci	iop_writeb(iop, IOP_ADDR_ALIVE, 0);
2058c2ecf20Sopenharmony_ci	return retval;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cistatic struct iop_msg *iop_get_unused_msg(void)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	int i;
2118c2ecf20Sopenharmony_ci	unsigned long flags;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	local_irq_save(flags);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	for (i = 0 ; i < NUM_IOP_MSGS ; i++) {
2168c2ecf20Sopenharmony_ci		if (iop_msg_pool[i].status == IOP_MSGSTATUS_UNUSED) {
2178c2ecf20Sopenharmony_ci			iop_msg_pool[i].status = IOP_MSGSTATUS_WAITING;
2188c2ecf20Sopenharmony_ci			local_irq_restore(flags);
2198c2ecf20Sopenharmony_ci			return &iop_msg_pool[i];
2208c2ecf20Sopenharmony_ci		}
2218c2ecf20Sopenharmony_ci	}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	local_irq_restore(flags);
2248c2ecf20Sopenharmony_ci	return NULL;
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci/*
2288c2ecf20Sopenharmony_ci * This is called by the startup code before anything else. Its purpose
2298c2ecf20Sopenharmony_ci * is to find and initialize the IOPs early in the boot sequence, so that
2308c2ecf20Sopenharmony_ci * the serial IOP can be placed into bypass mode _before_ we try to
2318c2ecf20Sopenharmony_ci * initialize the serial console.
2328c2ecf20Sopenharmony_ci */
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_civoid __init iop_preinit(void)
2358c2ecf20Sopenharmony_ci{
2368c2ecf20Sopenharmony_ci	if (macintosh_config->scc_type == MAC_SCC_IOP) {
2378c2ecf20Sopenharmony_ci		if (macintosh_config->ident == MAC_MODEL_IIFX) {
2388c2ecf20Sopenharmony_ci			iop_base[IOP_NUM_SCC] = (struct mac_iop *) SCC_IOP_BASE_IIFX;
2398c2ecf20Sopenharmony_ci		} else {
2408c2ecf20Sopenharmony_ci			iop_base[IOP_NUM_SCC] = (struct mac_iop *) SCC_IOP_BASE_QUADRA;
2418c2ecf20Sopenharmony_ci		}
2428c2ecf20Sopenharmony_ci		iop_scc_present = 1;
2438c2ecf20Sopenharmony_ci	} else {
2448c2ecf20Sopenharmony_ci		iop_base[IOP_NUM_SCC] = NULL;
2458c2ecf20Sopenharmony_ci		iop_scc_present = 0;
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci	if (macintosh_config->adb_type == MAC_ADB_IOP) {
2488c2ecf20Sopenharmony_ci		if (macintosh_config->ident == MAC_MODEL_IIFX) {
2498c2ecf20Sopenharmony_ci			iop_base[IOP_NUM_ISM] = (struct mac_iop *) ISM_IOP_BASE_IIFX;
2508c2ecf20Sopenharmony_ci		} else {
2518c2ecf20Sopenharmony_ci			iop_base[IOP_NUM_ISM] = (struct mac_iop *) ISM_IOP_BASE_QUADRA;
2528c2ecf20Sopenharmony_ci		}
2538c2ecf20Sopenharmony_ci		iop_stop(iop_base[IOP_NUM_ISM]);
2548c2ecf20Sopenharmony_ci		iop_ism_present = 1;
2558c2ecf20Sopenharmony_ci	} else {
2568c2ecf20Sopenharmony_ci		iop_base[IOP_NUM_ISM] = NULL;
2578c2ecf20Sopenharmony_ci		iop_ism_present = 0;
2588c2ecf20Sopenharmony_ci	}
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci/*
2628c2ecf20Sopenharmony_ci * Initialize the IOPs, if present.
2638c2ecf20Sopenharmony_ci */
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_civoid __init iop_init(void)
2668c2ecf20Sopenharmony_ci{
2678c2ecf20Sopenharmony_ci	int i;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	if (iop_scc_present) {
2708c2ecf20Sopenharmony_ci		pr_debug("SCC IOP detected at %p\n", iop_base[IOP_NUM_SCC]);
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci	if (iop_ism_present) {
2738c2ecf20Sopenharmony_ci		pr_debug("ISM IOP detected at %p\n", iop_base[IOP_NUM_ISM]);
2748c2ecf20Sopenharmony_ci		iop_start(iop_base[IOP_NUM_ISM]);
2758c2ecf20Sopenharmony_ci		iop_alive(iop_base[IOP_NUM_ISM]); /* clears the alive flag */
2768c2ecf20Sopenharmony_ci	}
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	/* Make the whole pool available and empty the queues */
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	for (i = 0 ; i < NUM_IOP_MSGS ; i++) {
2818c2ecf20Sopenharmony_ci		iop_msg_pool[i].status = IOP_MSGSTATUS_UNUSED;
2828c2ecf20Sopenharmony_ci	}
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	for (i = 0 ; i < NUM_IOP_CHAN ; i++) {
2858c2ecf20Sopenharmony_ci		iop_send_queue[IOP_NUM_SCC][i] = NULL;
2868c2ecf20Sopenharmony_ci		iop_send_queue[IOP_NUM_ISM][i] = NULL;
2878c2ecf20Sopenharmony_ci		iop_listeners[IOP_NUM_SCC][i].devname = NULL;
2888c2ecf20Sopenharmony_ci		iop_listeners[IOP_NUM_SCC][i].handler = NULL;
2898c2ecf20Sopenharmony_ci		iop_listeners[IOP_NUM_ISM][i].devname = NULL;
2908c2ecf20Sopenharmony_ci		iop_listeners[IOP_NUM_ISM][i].handler = NULL;
2918c2ecf20Sopenharmony_ci	}
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci/*
2958c2ecf20Sopenharmony_ci * Register the interrupt handler for the IOPs.
2968c2ecf20Sopenharmony_ci */
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_civoid __init iop_register_interrupts(void)
2998c2ecf20Sopenharmony_ci{
3008c2ecf20Sopenharmony_ci	if (iop_ism_present) {
3018c2ecf20Sopenharmony_ci		if (macintosh_config->ident == MAC_MODEL_IIFX) {
3028c2ecf20Sopenharmony_ci			if (request_irq(IRQ_MAC_ADB, iop_ism_irq, 0,
3038c2ecf20Sopenharmony_ci					"ISM IOP", (void *)IOP_NUM_ISM))
3048c2ecf20Sopenharmony_ci				pr_err("Couldn't register ISM IOP interrupt\n");
3058c2ecf20Sopenharmony_ci		} else {
3068c2ecf20Sopenharmony_ci			if (request_irq(IRQ_VIA2_0, iop_ism_irq, 0, "ISM IOP",
3078c2ecf20Sopenharmony_ci					(void *)IOP_NUM_ISM))
3088c2ecf20Sopenharmony_ci				pr_err("Couldn't register ISM IOP interrupt\n");
3098c2ecf20Sopenharmony_ci		}
3108c2ecf20Sopenharmony_ci		if (!iop_alive(iop_base[IOP_NUM_ISM])) {
3118c2ecf20Sopenharmony_ci			pr_warn("IOP: oh my god, they killed the ISM IOP!\n");
3128c2ecf20Sopenharmony_ci		} else {
3138c2ecf20Sopenharmony_ci			pr_warn("IOP: the ISM IOP seems to be alive.\n");
3148c2ecf20Sopenharmony_ci		}
3158c2ecf20Sopenharmony_ci	}
3168c2ecf20Sopenharmony_ci}
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci/*
3198c2ecf20Sopenharmony_ci * Register or unregister a listener for a specific IOP and channel
3208c2ecf20Sopenharmony_ci *
3218c2ecf20Sopenharmony_ci * If the handler pointer is NULL the current listener (if any) is
3228c2ecf20Sopenharmony_ci * unregistered. Otherwise the new listener is registered provided
3238c2ecf20Sopenharmony_ci * there is no existing listener registered.
3248c2ecf20Sopenharmony_ci */
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ciint iop_listen(uint iop_num, uint chan,
3278c2ecf20Sopenharmony_ci		void (*handler)(struct iop_msg *),
3288c2ecf20Sopenharmony_ci		const char *devname)
3298c2ecf20Sopenharmony_ci{
3308c2ecf20Sopenharmony_ci	if ((iop_num >= NUM_IOPS) || !iop_base[iop_num]) return -EINVAL;
3318c2ecf20Sopenharmony_ci	if (chan >= NUM_IOP_CHAN) return -EINVAL;
3328c2ecf20Sopenharmony_ci	if (iop_listeners[iop_num][chan].handler && handler) return -EINVAL;
3338c2ecf20Sopenharmony_ci	iop_listeners[iop_num][chan].devname = devname;
3348c2ecf20Sopenharmony_ci	iop_listeners[iop_num][chan].handler = handler;
3358c2ecf20Sopenharmony_ci	return 0;
3368c2ecf20Sopenharmony_ci}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci/*
3398c2ecf20Sopenharmony_ci * Complete reception of a message, which just means copying the reply
3408c2ecf20Sopenharmony_ci * into the buffer, setting the channel state to MSG_COMPLETE and
3418c2ecf20Sopenharmony_ci * notifying the IOP.
3428c2ecf20Sopenharmony_ci */
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_civoid iop_complete_message(struct iop_msg *msg)
3458c2ecf20Sopenharmony_ci{
3468c2ecf20Sopenharmony_ci	int iop_num = msg->iop_num;
3478c2ecf20Sopenharmony_ci	int chan = msg->channel;
3488c2ecf20Sopenharmony_ci	int i,offset;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	iop_pr_debug("iop_num %d chan %d reply %*ph\n",
3518c2ecf20Sopenharmony_ci		     msg->iop_num, msg->channel, IOP_MSG_LEN, msg->reply);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	offset = IOP_ADDR_RECV_MSG + (msg->channel * IOP_MSG_LEN);
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_ci	for (i = 0 ; i < IOP_MSG_LEN ; i++, offset++) {
3568c2ecf20Sopenharmony_ci		iop_writeb(iop_base[iop_num], offset, msg->reply[i]);
3578c2ecf20Sopenharmony_ci	}
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	iop_writeb(iop_base[iop_num],
3608c2ecf20Sopenharmony_ci		   IOP_ADDR_RECV_STATE + chan, IOP_MSG_COMPLETE);
3618c2ecf20Sopenharmony_ci	iop_interrupt(iop_base[msg->iop_num]);
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	msg->status = IOP_MSGSTATUS_UNUSED;
3648c2ecf20Sopenharmony_ci}
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci/*
3678c2ecf20Sopenharmony_ci * Actually put a message into a send channel buffer
3688c2ecf20Sopenharmony_ci */
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_cistatic void iop_do_send(struct iop_msg *msg)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	volatile struct mac_iop *iop = iop_base[msg->iop_num];
3738c2ecf20Sopenharmony_ci	int i,offset;
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	iop_pr_debug("iop_num %d chan %d message %*ph\n",
3768c2ecf20Sopenharmony_ci		     msg->iop_num, msg->channel, IOP_MSG_LEN, msg->message);
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	offset = IOP_ADDR_SEND_MSG + (msg->channel * IOP_MSG_LEN);
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	for (i = 0 ; i < IOP_MSG_LEN ; i++, offset++) {
3818c2ecf20Sopenharmony_ci		iop_writeb(iop, offset, msg->message[i]);
3828c2ecf20Sopenharmony_ci	}
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci	iop_writeb(iop, IOP_ADDR_SEND_STATE + msg->channel, IOP_MSG_NEW);
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci	iop_interrupt(iop);
3878c2ecf20Sopenharmony_ci}
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci/*
3908c2ecf20Sopenharmony_ci * Handle sending a message on a channel that
3918c2ecf20Sopenharmony_ci * has gone into the IOP_MSG_COMPLETE state.
3928c2ecf20Sopenharmony_ci */
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_cistatic void iop_handle_send(uint iop_num, uint chan)
3958c2ecf20Sopenharmony_ci{
3968c2ecf20Sopenharmony_ci	volatile struct mac_iop *iop = iop_base[iop_num];
3978c2ecf20Sopenharmony_ci	struct iop_msg *msg;
3988c2ecf20Sopenharmony_ci	int i,offset;
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci	iop_writeb(iop, IOP_ADDR_SEND_STATE + chan, IOP_MSG_IDLE);
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	if (!(msg = iop_send_queue[iop_num][chan])) return;
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	msg->status = IOP_MSGSTATUS_COMPLETE;
4058c2ecf20Sopenharmony_ci	offset = IOP_ADDR_SEND_MSG + (chan * IOP_MSG_LEN);
4068c2ecf20Sopenharmony_ci	for (i = 0 ; i < IOP_MSG_LEN ; i++, offset++) {
4078c2ecf20Sopenharmony_ci		msg->reply[i] = iop_readb(iop, offset);
4088c2ecf20Sopenharmony_ci	}
4098c2ecf20Sopenharmony_ci	iop_pr_debug("iop_num %d chan %d reply %*ph\n",
4108c2ecf20Sopenharmony_ci		     iop_num, chan, IOP_MSG_LEN, msg->reply);
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	if (msg->handler) (*msg->handler)(msg);
4138c2ecf20Sopenharmony_ci	msg->status = IOP_MSGSTATUS_UNUSED;
4148c2ecf20Sopenharmony_ci	msg = msg->next;
4158c2ecf20Sopenharmony_ci	iop_send_queue[iop_num][chan] = msg;
4168c2ecf20Sopenharmony_ci	if (msg && iop_readb(iop, IOP_ADDR_SEND_STATE + chan) == IOP_MSG_IDLE)
4178c2ecf20Sopenharmony_ci		iop_do_send(msg);
4188c2ecf20Sopenharmony_ci}
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci/*
4218c2ecf20Sopenharmony_ci * Handle reception of a message on a channel that has
4228c2ecf20Sopenharmony_ci * gone into the IOP_MSG_NEW state.
4238c2ecf20Sopenharmony_ci */
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_cistatic void iop_handle_recv(uint iop_num, uint chan)
4268c2ecf20Sopenharmony_ci{
4278c2ecf20Sopenharmony_ci	volatile struct mac_iop *iop = iop_base[iop_num];
4288c2ecf20Sopenharmony_ci	int i,offset;
4298c2ecf20Sopenharmony_ci	struct iop_msg *msg;
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	msg = iop_get_unused_msg();
4328c2ecf20Sopenharmony_ci	msg->iop_num = iop_num;
4338c2ecf20Sopenharmony_ci	msg->channel = chan;
4348c2ecf20Sopenharmony_ci	msg->status = IOP_MSGSTATUS_UNSOL;
4358c2ecf20Sopenharmony_ci	msg->handler = iop_listeners[iop_num][chan].handler;
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	offset = IOP_ADDR_RECV_MSG + (chan * IOP_MSG_LEN);
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	for (i = 0 ; i < IOP_MSG_LEN ; i++, offset++) {
4408c2ecf20Sopenharmony_ci		msg->message[i] = iop_readb(iop, offset);
4418c2ecf20Sopenharmony_ci	}
4428c2ecf20Sopenharmony_ci	iop_pr_debug("iop_num %d chan %d message %*ph\n",
4438c2ecf20Sopenharmony_ci		     iop_num, chan, IOP_MSG_LEN, msg->message);
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ci	iop_writeb(iop, IOP_ADDR_RECV_STATE + chan, IOP_MSG_RCVD);
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	/* If there is a listener, call it now. Otherwise complete */
4488c2ecf20Sopenharmony_ci	/* the message ourselves to avoid possible stalls.         */
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	if (msg->handler) {
4518c2ecf20Sopenharmony_ci		(*msg->handler)(msg);
4528c2ecf20Sopenharmony_ci	} else {
4538c2ecf20Sopenharmony_ci		memset(msg->reply, 0, IOP_MSG_LEN);
4548c2ecf20Sopenharmony_ci		iop_complete_message(msg);
4558c2ecf20Sopenharmony_ci	}
4568c2ecf20Sopenharmony_ci}
4578c2ecf20Sopenharmony_ci
4588c2ecf20Sopenharmony_ci/*
4598c2ecf20Sopenharmony_ci * Send a message
4608c2ecf20Sopenharmony_ci *
4618c2ecf20Sopenharmony_ci * The message is placed at the end of the send queue. Afterwards if the
4628c2ecf20Sopenharmony_ci * channel is idle we force an immediate send of the next message in the
4638c2ecf20Sopenharmony_ci * queue.
4648c2ecf20Sopenharmony_ci */
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ciint iop_send_message(uint iop_num, uint chan, void *privdata,
4678c2ecf20Sopenharmony_ci		      uint msg_len, __u8 *msg_data,
4688c2ecf20Sopenharmony_ci		      void (*handler)(struct iop_msg *))
4698c2ecf20Sopenharmony_ci{
4708c2ecf20Sopenharmony_ci	struct iop_msg *msg, *q;
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	if ((iop_num >= NUM_IOPS) || !iop_base[iop_num]) return -EINVAL;
4738c2ecf20Sopenharmony_ci	if (chan >= NUM_IOP_CHAN) return -EINVAL;
4748c2ecf20Sopenharmony_ci	if (msg_len > IOP_MSG_LEN) return -EINVAL;
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	msg = iop_get_unused_msg();
4778c2ecf20Sopenharmony_ci	if (!msg) return -ENOMEM;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	msg->next = NULL;
4808c2ecf20Sopenharmony_ci	msg->status = IOP_MSGSTATUS_WAITING;
4818c2ecf20Sopenharmony_ci	msg->iop_num = iop_num;
4828c2ecf20Sopenharmony_ci	msg->channel = chan;
4838c2ecf20Sopenharmony_ci	msg->caller_priv = privdata;
4848c2ecf20Sopenharmony_ci	memcpy(msg->message, msg_data, msg_len);
4858c2ecf20Sopenharmony_ci	msg->handler = handler;
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci	if (!(q = iop_send_queue[iop_num][chan])) {
4888c2ecf20Sopenharmony_ci		iop_send_queue[iop_num][chan] = msg;
4898c2ecf20Sopenharmony_ci		iop_do_send(msg);
4908c2ecf20Sopenharmony_ci	} else {
4918c2ecf20Sopenharmony_ci		while (q->next) q = q->next;
4928c2ecf20Sopenharmony_ci		q->next = msg;
4938c2ecf20Sopenharmony_ci	}
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	return 0;
4968c2ecf20Sopenharmony_ci}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci/*
4998c2ecf20Sopenharmony_ci * Upload code to the shared RAM of an IOP.
5008c2ecf20Sopenharmony_ci */
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_civoid iop_upload_code(uint iop_num, __u8 *code_start,
5038c2ecf20Sopenharmony_ci		     uint code_len, __u16 shared_ram_start)
5048c2ecf20Sopenharmony_ci{
5058c2ecf20Sopenharmony_ci	if ((iop_num >= NUM_IOPS) || !iop_base[iop_num]) return;
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci	iop_loadaddr(iop_base[iop_num], shared_ram_start);
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci	while (code_len--) {
5108c2ecf20Sopenharmony_ci		iop_base[iop_num]->ram_data = *code_start++;
5118c2ecf20Sopenharmony_ci	}
5128c2ecf20Sopenharmony_ci}
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci/*
5158c2ecf20Sopenharmony_ci * Download code from the shared RAM of an IOP.
5168c2ecf20Sopenharmony_ci */
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_civoid iop_download_code(uint iop_num, __u8 *code_start,
5198c2ecf20Sopenharmony_ci		       uint code_len, __u16 shared_ram_start)
5208c2ecf20Sopenharmony_ci{
5218c2ecf20Sopenharmony_ci	if ((iop_num >= NUM_IOPS) || !iop_base[iop_num]) return;
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	iop_loadaddr(iop_base[iop_num], shared_ram_start);
5248c2ecf20Sopenharmony_ci
5258c2ecf20Sopenharmony_ci	while (code_len--) {
5268c2ecf20Sopenharmony_ci		*code_start++ = iop_base[iop_num]->ram_data;
5278c2ecf20Sopenharmony_ci	}
5288c2ecf20Sopenharmony_ci}
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ci/*
5318c2ecf20Sopenharmony_ci * Compare the code in the shared RAM of an IOP with a copy in system memory
5328c2ecf20Sopenharmony_ci * and return 0 on match or the first nonmatching system memory address on
5338c2ecf20Sopenharmony_ci * failure.
5348c2ecf20Sopenharmony_ci */
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci__u8 *iop_compare_code(uint iop_num, __u8 *code_start,
5378c2ecf20Sopenharmony_ci		       uint code_len, __u16 shared_ram_start)
5388c2ecf20Sopenharmony_ci{
5398c2ecf20Sopenharmony_ci	if ((iop_num >= NUM_IOPS) || !iop_base[iop_num]) return code_start;
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	iop_loadaddr(iop_base[iop_num], shared_ram_start);
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_ci	while (code_len--) {
5448c2ecf20Sopenharmony_ci		if (*code_start != iop_base[iop_num]->ram_data) {
5458c2ecf20Sopenharmony_ci			return code_start;
5468c2ecf20Sopenharmony_ci		}
5478c2ecf20Sopenharmony_ci		code_start++;
5488c2ecf20Sopenharmony_ci	}
5498c2ecf20Sopenharmony_ci	return (__u8 *) 0;
5508c2ecf20Sopenharmony_ci}
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci/*
5538c2ecf20Sopenharmony_ci * Handle an ISM IOP interrupt
5548c2ecf20Sopenharmony_ci */
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ciirqreturn_t iop_ism_irq(int irq, void *dev_id)
5578c2ecf20Sopenharmony_ci{
5588c2ecf20Sopenharmony_ci	uint iop_num = (uint) dev_id;
5598c2ecf20Sopenharmony_ci	volatile struct mac_iop *iop = iop_base[iop_num];
5608c2ecf20Sopenharmony_ci	int i,state;
5618c2ecf20Sopenharmony_ci	u8 events = iop->status_ctrl & (IOP_INT0 | IOP_INT1);
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	do {
5648c2ecf20Sopenharmony_ci		iop_pr_debug("iop_num %d status %02X\n", iop_num,
5658c2ecf20Sopenharmony_ci			     iop->status_ctrl);
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_ci		/* INT0 indicates state change on an outgoing message channel */
5688c2ecf20Sopenharmony_ci		if (events & IOP_INT0) {
5698c2ecf20Sopenharmony_ci			iop->status_ctrl = IOP_INT0 | IOP_RUN | IOP_AUTOINC;
5708c2ecf20Sopenharmony_ci			for (i = 0; i < NUM_IOP_CHAN; i++) {
5718c2ecf20Sopenharmony_ci				state = iop_readb(iop, IOP_ADDR_SEND_STATE + i);
5728c2ecf20Sopenharmony_ci				if (state == IOP_MSG_COMPLETE)
5738c2ecf20Sopenharmony_ci					iop_handle_send(iop_num, i);
5748c2ecf20Sopenharmony_ci				else if (state != IOP_MSG_IDLE)
5758c2ecf20Sopenharmony_ci					iop_pr_debug("chan %d send state %02X\n",
5768c2ecf20Sopenharmony_ci						     i, state);
5778c2ecf20Sopenharmony_ci			}
5788c2ecf20Sopenharmony_ci		}
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_ci		/* INT1 for incoming messages */
5818c2ecf20Sopenharmony_ci		if (events & IOP_INT1) {
5828c2ecf20Sopenharmony_ci			iop->status_ctrl = IOP_INT1 | IOP_RUN | IOP_AUTOINC;
5838c2ecf20Sopenharmony_ci			for (i = 0; i < NUM_IOP_CHAN; i++) {
5848c2ecf20Sopenharmony_ci				state = iop_readb(iop, IOP_ADDR_RECV_STATE + i);
5858c2ecf20Sopenharmony_ci				if (state == IOP_MSG_NEW)
5868c2ecf20Sopenharmony_ci					iop_handle_recv(iop_num, i);
5878c2ecf20Sopenharmony_ci				else if (state != IOP_MSG_IDLE)
5888c2ecf20Sopenharmony_ci					iop_pr_debug("chan %d recv state %02X\n",
5898c2ecf20Sopenharmony_ci						     i, state);
5908c2ecf20Sopenharmony_ci			}
5918c2ecf20Sopenharmony_ci		}
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci		events = iop->status_ctrl & (IOP_INT0 | IOP_INT1);
5948c2ecf20Sopenharmony_ci	} while (events);
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
5978c2ecf20Sopenharmony_ci}
5988c2ecf20Sopenharmony_ci
5998c2ecf20Sopenharmony_civoid iop_ism_irq_poll(uint iop_num)
6008c2ecf20Sopenharmony_ci{
6018c2ecf20Sopenharmony_ci	unsigned long flags;
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci	local_irq_save(flags);
6048c2ecf20Sopenharmony_ci	iop_ism_irq(0, (void *)iop_num);
6058c2ecf20Sopenharmony_ci	local_irq_restore(flags);
6068c2ecf20Sopenharmony_ci}
607