| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  *  arch/arm/mac-sa1100/jornada720_ssp.c | 
 |  * | 
 |  *  Copyright (C) 2006/2007 Kristoffer Ericson <Kristoffer.Ericson@gmail.com> | 
 |  *   Copyright (C) 2006 Filip Zyzniewski <filip.zyzniewski@tefnet.pl> | 
 |  * | 
 |  *  SSP driver for the HP Jornada 710/720/728 | 
 |  */ | 
 |  | 
 | #include <linux/delay.h> | 
 | #include <linux/errno.h> | 
 | #include <linux/init.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/sched.h> | 
 | #include <linux/io.h> | 
 |  | 
 | #include <mach/hardware.h> | 
 | #include <mach/jornada720.h> | 
 | #include <asm/hardware/ssp.h> | 
 |  | 
 | static DEFINE_SPINLOCK(jornada_ssp_lock); | 
 | static unsigned long jornada_ssp_flags; | 
 |  | 
 | /** | 
 |  * jornada_ssp_reverse - reverses input byte | 
 |  * @byte: input byte to reverse | 
 |  * | 
 |  * we need to reverse all data we receive from the mcu due to its physical location | 
 |  * returns : 01110111 -> 11101110 | 
 |  */ | 
 | inline u8 jornada_ssp_reverse(u8 byte) | 
 | { | 
 | 	return | 
 | 		((0x80 & byte) >> 7) | | 
 | 		((0x40 & byte) >> 5) | | 
 | 		((0x20 & byte) >> 3) | | 
 | 		((0x10 & byte) >> 1) | | 
 | 		((0x08 & byte) << 1) | | 
 | 		((0x04 & byte) << 3) | | 
 | 		((0x02 & byte) << 5) | | 
 | 		((0x01 & byte) << 7); | 
 | }; | 
 | EXPORT_SYMBOL(jornada_ssp_reverse); | 
 |  | 
 | /** | 
 |  * jornada_ssp_byte - waits for ready ssp bus and sends byte | 
 |  * @byte: input byte to transmit | 
 |  * | 
 |  * waits for fifo buffer to clear and then transmits, if it doesn't then we will | 
 |  * timeout after <timeout> rounds. Needs mcu running before its called. | 
 |  * | 
 |  * returns : %mcu output on success | 
 |  *	   : %-ETIMEDOUT on timeout | 
 |  */ | 
 | int jornada_ssp_byte(u8 byte) | 
 | { | 
 | 	int timeout = 400000; | 
 | 	u16 ret; | 
 |  | 
 | 	while ((GPLR & GPIO_GPIO10)) { | 
 | 		if (!--timeout) { | 
 | 			printk(KERN_WARNING "SSP: timeout while waiting for transmit\n"); | 
 | 			return -ETIMEDOUT; | 
 | 		} | 
 | 		cpu_relax(); | 
 | 	} | 
 |  | 
 | 	ret = jornada_ssp_reverse(byte) << 8; | 
 |  | 
 | 	ssp_write_word(ret); | 
 | 	ssp_read_word(&ret); | 
 |  | 
 | 	return jornada_ssp_reverse(ret); | 
 | }; | 
 | EXPORT_SYMBOL(jornada_ssp_byte); | 
 |  | 
 | /** | 
 |  * jornada_ssp_inout - decide if input is command or trading byte | 
 |  * @byte: input byte to send (may be %TXDUMMY) | 
 |  * | 
 |  * returns : (jornada_ssp_byte(byte)) on success | 
 |  *         : %-ETIMEDOUT on timeout failure | 
 |  */ | 
 | int jornada_ssp_inout(u8 byte) | 
 | { | 
 | 	int ret, i; | 
 |  | 
 | 	/* true means command byte */ | 
 | 	if (byte != TXDUMMY) { | 
 | 		ret = jornada_ssp_byte(byte); | 
 | 		/* Proper return to commands is TxDummy */ | 
 | 		if (ret != TXDUMMY) { | 
 | 			for (i = 0; i < 256; i++)/* flushing bus */ | 
 | 				if (jornada_ssp_byte(TXDUMMY) == -1) | 
 | 					break; | 
 | 			return -ETIMEDOUT; | 
 | 		} | 
 | 	} else /* Exchange TxDummy for data */ | 
 | 		ret = jornada_ssp_byte(TXDUMMY); | 
 |  | 
 | 	return ret; | 
 | }; | 
 | EXPORT_SYMBOL(jornada_ssp_inout); | 
 |  | 
 | /** | 
 |  * jornada_ssp_start - enable mcu | 
 |  * | 
 |  */ | 
 | void jornada_ssp_start(void) | 
 | { | 
 | 	spin_lock_irqsave(&jornada_ssp_lock, jornada_ssp_flags); | 
 | 	GPCR = GPIO_GPIO25; | 
 | 	udelay(50); | 
 | 	return; | 
 | }; | 
 | EXPORT_SYMBOL(jornada_ssp_start); | 
 |  | 
 | /** | 
 |  * jornada_ssp_end - disable mcu and turn off lock | 
 |  * | 
 |  */ | 
 | void jornada_ssp_end(void) | 
 | { | 
 | 	GPSR = GPIO_GPIO25; | 
 | 	spin_unlock_irqrestore(&jornada_ssp_lock, jornada_ssp_flags); | 
 | 	return; | 
 | }; | 
 | EXPORT_SYMBOL(jornada_ssp_end); | 
 |  | 
 | static int jornada_ssp_probe(struct platform_device *dev) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	GPSR = GPIO_GPIO25; | 
 |  | 
 | 	ret = ssp_init(); | 
 |  | 
 | 	/* worked fine, lets not bother with anything else */ | 
 | 	if (!ret) { | 
 | 		printk(KERN_INFO "SSP: device initialized with irq\n"); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	printk(KERN_WARNING "SSP: initialization failed, trying non-irq solution \n"); | 
 |  | 
 | 	/* init of Serial 4 port */ | 
 | 	Ser4MCCR0 = 0; | 
 | 	Ser4SSCR0 = 0x0387; | 
 | 	Ser4SSCR1 = 0x18; | 
 |  | 
 | 	/* clear out any left over data */ | 
 | 	ssp_flush(); | 
 |  | 
 | 	/* enable MCU */ | 
 | 	jornada_ssp_start(); | 
 |  | 
 | 	/* see if return value makes sense */ | 
 | 	ret = jornada_ssp_inout(GETBRIGHTNESS); | 
 |  | 
 | 	/* seems like it worked, just feed it with TxDummy to get rid of data */ | 
 | 	if (ret == TXDUMMY) | 
 | 		jornada_ssp_inout(TXDUMMY); | 
 |  | 
 | 	jornada_ssp_end(); | 
 |  | 
 | 	/* failed, lets just kill everything */ | 
 | 	if (ret == -ETIMEDOUT) { | 
 | 		printk(KERN_WARNING "SSP: attempts failed, bailing\n"); | 
 | 		ssp_exit(); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	/* all fine */ | 
 | 	printk(KERN_INFO "SSP: device initialized\n"); | 
 | 	return 0; | 
 | }; | 
 |  | 
 | static void jornada_ssp_remove(struct platform_device *dev) | 
 | { | 
 | 	/* Note that this doesn't actually remove the driver, since theres nothing to remove | 
 | 	 * It just makes sure everything is turned off */ | 
 | 	GPSR = GPIO_GPIO25; | 
 | 	ssp_exit(); | 
 | }; | 
 |  | 
 | struct platform_driver jornadassp_driver = { | 
 | 	.probe	= jornada_ssp_probe, | 
 | 	.remove_new = jornada_ssp_remove, | 
 | 	.driver	= { | 
 | 		.name	= "jornada_ssp", | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __init jornada_ssp_init(void) | 
 | { | 
 | 	return platform_driver_register(&jornadassp_driver); | 
 | } | 
 |  | 
 | module_init(jornada_ssp_init); |