18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  linux/arch/arm/mach-sa1100/ssp.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2003 Russell King.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *  Generic SSP driver.  This provides the generic core for simple
88c2ecf20Sopenharmony_ci *  IO-based SSP applications.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/sched.h>
138c2ecf20Sopenharmony_ci#include <linux/errno.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/ioport.h>
168c2ecf20Sopenharmony_ci#include <linux/init.h>
178c2ecf20Sopenharmony_ci#include <linux/io.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <mach/hardware.h>
208c2ecf20Sopenharmony_ci#include <mach/irqs.h>
218c2ecf20Sopenharmony_ci#include <asm/hardware/ssp.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define TIMEOUT 100000
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic irqreturn_t ssp_interrupt(int irq, void *dev_id)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	unsigned int status = Ser4SSSR;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	if (status & SSSR_ROR)
308c2ecf20Sopenharmony_ci		printk(KERN_WARNING "SSP: receiver overrun\n");
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	Ser4SSSR = SSSR_ROR;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	return status ? IRQ_HANDLED : IRQ_NONE;
358c2ecf20Sopenharmony_ci}
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/**
388c2ecf20Sopenharmony_ci * ssp_write_word - write a word to the SSP port
398c2ecf20Sopenharmony_ci * @data: 16-bit, MSB justified data to write.
408c2ecf20Sopenharmony_ci *
418c2ecf20Sopenharmony_ci * Wait for a free entry in the SSP transmit FIFO, and write a data
428c2ecf20Sopenharmony_ci * word to the SSP port.  Wait for the SSP port to start sending
438c2ecf20Sopenharmony_ci * the data.
448c2ecf20Sopenharmony_ci *
458c2ecf20Sopenharmony_ci * The caller is expected to perform the necessary locking.
468c2ecf20Sopenharmony_ci *
478c2ecf20Sopenharmony_ci * Returns:
488c2ecf20Sopenharmony_ci *   %-ETIMEDOUT	timeout occurred
498c2ecf20Sopenharmony_ci *   0			success
508c2ecf20Sopenharmony_ci */
518c2ecf20Sopenharmony_ciint ssp_write_word(u16 data)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	int timeout = TIMEOUT;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	while (!(Ser4SSSR & SSSR_TNF)) {
568c2ecf20Sopenharmony_ci	        if (!--timeout)
578c2ecf20Sopenharmony_ci	        	return -ETIMEDOUT;
588c2ecf20Sopenharmony_ci		cpu_relax();
598c2ecf20Sopenharmony_ci	}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	Ser4SSDR = data;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	timeout = TIMEOUT;
648c2ecf20Sopenharmony_ci	while (!(Ser4SSSR & SSSR_BSY)) {
658c2ecf20Sopenharmony_ci	        if (!--timeout)
668c2ecf20Sopenharmony_ci	        	return -ETIMEDOUT;
678c2ecf20Sopenharmony_ci		cpu_relax();
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	return 0;
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci/**
748c2ecf20Sopenharmony_ci * ssp_read_word - read a word from the SSP port
758c2ecf20Sopenharmony_ci *
768c2ecf20Sopenharmony_ci * Wait for a data word in the SSP receive FIFO, and return the
778c2ecf20Sopenharmony_ci * received data.  Data is LSB justified.
788c2ecf20Sopenharmony_ci *
798c2ecf20Sopenharmony_ci * Note: Currently, if data is not expected to be received, this
808c2ecf20Sopenharmony_ci * function will wait for ever.
818c2ecf20Sopenharmony_ci *
828c2ecf20Sopenharmony_ci * The caller is expected to perform the necessary locking.
838c2ecf20Sopenharmony_ci *
848c2ecf20Sopenharmony_ci * Returns:
858c2ecf20Sopenharmony_ci *   %-ETIMEDOUT	timeout occurred
868c2ecf20Sopenharmony_ci *   16-bit data	success
878c2ecf20Sopenharmony_ci */
888c2ecf20Sopenharmony_ciint ssp_read_word(u16 *data)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	int timeout = TIMEOUT;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	while (!(Ser4SSSR & SSSR_RNE)) {
938c2ecf20Sopenharmony_ci	        if (!--timeout)
948c2ecf20Sopenharmony_ci	        	return -ETIMEDOUT;
958c2ecf20Sopenharmony_ci		cpu_relax();
968c2ecf20Sopenharmony_ci	}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	*data = (u16)Ser4SSDR;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	return 0;
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/**
1048c2ecf20Sopenharmony_ci * ssp_flush - flush the transmit and receive FIFOs
1058c2ecf20Sopenharmony_ci *
1068c2ecf20Sopenharmony_ci * Wait for the SSP to idle, and ensure that the receive FIFO
1078c2ecf20Sopenharmony_ci * is empty.
1088c2ecf20Sopenharmony_ci *
1098c2ecf20Sopenharmony_ci * The caller is expected to perform the necessary locking.
1108c2ecf20Sopenharmony_ci *
1118c2ecf20Sopenharmony_ci * Returns:
1128c2ecf20Sopenharmony_ci *   %-ETIMEDOUT	timeout occurred
1138c2ecf20Sopenharmony_ci *   0			success
1148c2ecf20Sopenharmony_ci */
1158c2ecf20Sopenharmony_ciint ssp_flush(void)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	int timeout = TIMEOUT * 2;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	do {
1208c2ecf20Sopenharmony_ci		while (Ser4SSSR & SSSR_RNE) {
1218c2ecf20Sopenharmony_ci		        if (!--timeout)
1228c2ecf20Sopenharmony_ci		        	return -ETIMEDOUT;
1238c2ecf20Sopenharmony_ci			(void) Ser4SSDR;
1248c2ecf20Sopenharmony_ci		}
1258c2ecf20Sopenharmony_ci	        if (!--timeout)
1268c2ecf20Sopenharmony_ci	        	return -ETIMEDOUT;
1278c2ecf20Sopenharmony_ci	} while (Ser4SSSR & SSSR_BSY);
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	return 0;
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci/**
1338c2ecf20Sopenharmony_ci * ssp_enable - enable the SSP port
1348c2ecf20Sopenharmony_ci *
1358c2ecf20Sopenharmony_ci * Turn on the SSP port.
1368c2ecf20Sopenharmony_ci */
1378c2ecf20Sopenharmony_civoid ssp_enable(void)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	Ser4SSCR0 |= SSCR0_SSE;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci/**
1438c2ecf20Sopenharmony_ci * ssp_disable - shut down the SSP port
1448c2ecf20Sopenharmony_ci *
1458c2ecf20Sopenharmony_ci * Turn off the SSP port, optionally powering it down.
1468c2ecf20Sopenharmony_ci */
1478c2ecf20Sopenharmony_civoid ssp_disable(void)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	Ser4SSCR0 &= ~SSCR0_SSE;
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci/**
1538c2ecf20Sopenharmony_ci * ssp_save_state - save the SSP configuration
1548c2ecf20Sopenharmony_ci * @ssp: pointer to structure to save SSP configuration
1558c2ecf20Sopenharmony_ci *
1568c2ecf20Sopenharmony_ci * Save the configured SSP state for suspend.
1578c2ecf20Sopenharmony_ci */
1588c2ecf20Sopenharmony_civoid ssp_save_state(struct ssp_state *ssp)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	ssp->cr0 = Ser4SSCR0;
1618c2ecf20Sopenharmony_ci	ssp->cr1 = Ser4SSCR1;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	Ser4SSCR0 &= ~SSCR0_SSE;
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci/**
1678c2ecf20Sopenharmony_ci * ssp_restore_state - restore a previously saved SSP configuration
1688c2ecf20Sopenharmony_ci * @ssp: pointer to configuration saved by ssp_save_state
1698c2ecf20Sopenharmony_ci *
1708c2ecf20Sopenharmony_ci * Restore the SSP configuration saved previously by ssp_save_state.
1718c2ecf20Sopenharmony_ci */
1728c2ecf20Sopenharmony_civoid ssp_restore_state(struct ssp_state *ssp)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	Ser4SSSR = SSSR_ROR;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	Ser4SSCR0 = ssp->cr0 & ~SSCR0_SSE;
1778c2ecf20Sopenharmony_ci	Ser4SSCR1 = ssp->cr1;
1788c2ecf20Sopenharmony_ci	Ser4SSCR0 = ssp->cr0;
1798c2ecf20Sopenharmony_ci}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci/**
1828c2ecf20Sopenharmony_ci * ssp_init - setup the SSP port
1838c2ecf20Sopenharmony_ci *
1848c2ecf20Sopenharmony_ci * initialise and claim resources for the SSP port.
1858c2ecf20Sopenharmony_ci *
1868c2ecf20Sopenharmony_ci * Returns:
1878c2ecf20Sopenharmony_ci *   %-ENODEV	if the SSP port is unavailable
1888c2ecf20Sopenharmony_ci *   %-EBUSY	if the resources are already in use
1898c2ecf20Sopenharmony_ci *   %0		on success
1908c2ecf20Sopenharmony_ci */
1918c2ecf20Sopenharmony_ciint ssp_init(void)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	int ret;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	if (!(PPAR & PPAR_SPR) && (Ser4MCCR0 & MCCR0_MCE))
1968c2ecf20Sopenharmony_ci		return -ENODEV;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	if (!request_mem_region(__PREG(Ser4SSCR0), 0x18, "SSP")) {
1998c2ecf20Sopenharmony_ci		return -EBUSY;
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	Ser4SSSR = SSSR_ROR;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	ret = request_irq(IRQ_Ser4SSP, ssp_interrupt, 0, "SSP", NULL);
2058c2ecf20Sopenharmony_ci	if (ret)
2068c2ecf20Sopenharmony_ci		goto out_region;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	return 0;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci out_region:
2118c2ecf20Sopenharmony_ci	release_mem_region(__PREG(Ser4SSCR0), 0x18);
2128c2ecf20Sopenharmony_ci	return ret;
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci/**
2168c2ecf20Sopenharmony_ci * ssp_exit - undo the effects of ssp_init
2178c2ecf20Sopenharmony_ci *
2188c2ecf20Sopenharmony_ci * release and free resources for the SSP port.
2198c2ecf20Sopenharmony_ci */
2208c2ecf20Sopenharmony_civoid ssp_exit(void)
2218c2ecf20Sopenharmony_ci{
2228c2ecf20Sopenharmony_ci	Ser4SSCR0 &= ~SSCR0_SSE;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	free_irq(IRQ_Ser4SSP, NULL);
2258c2ecf20Sopenharmony_ci	release_mem_region(__PREG(Ser4SSCR0), 0x18);
2268c2ecf20Sopenharmony_ci}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ciMODULE_AUTHOR("Russell King");
2298c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SA11x0 SSP PIO driver");
2308c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_write_word);
2338c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_read_word);
2348c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_flush);
2358c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_enable);
2368c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_disable);
2378c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_save_state);
2388c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_restore_state);
2398c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_init);
2408c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ssp_exit);
241