/*
   iomacros.h

   Contributors:
     Created by Marek Michalkiewicz <marekm@linux.org.pl>

   THIS SOFTWARE IS NOT COPYRIGHTED

   This source code is offered for use in the public domain.  You may
   use, modify or distribute it freely.

   This code is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY.  ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
   DISCLAIMED.  This includes but is not limited to warranties of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef _IOMACROS_H_
#define _IOMACROS_H_

#include <inttypes.h>

#ifndef BV
  #define BV(x) (1 << (x))
#endif

#ifndef __SFR_OFFSET
#define __SFR_OFFSET 0
#endif /* !__SFR_OFFSET */

/* Memory mapped I/O - use for non-constant I/O space addresses.  */

#define __mmio(port) \
	(*((volatile uint8_t *)((port) - __SFR_OFFSET + 0x20)))

#define __mmio_word(port) \
	(*((volatile uint16_t *)((port) - __SFR_OFFSET + 0x20)))

/* Macros for access to 8-bit I/O registers with constant address.  */

/* Note: constraints "I" changed to "M" to avoid warnings for ATmega128
   I/O registers.  You should still get assembler errors if you try
   to access I/O registers outside the range for in/out instructions,
   but normally GCC should optimize away all such instructions.  */

#define __inb(port) ({				\
	uint8_t __t;				\
	__asm__ __volatile__ (			\
		"in %0,%1"			\
		: "=r" (__t)			\
		: "M" ((uint8_t)((port)-__SFR_OFFSET))	\
	);					\
	__t;					\
 })

/* XXX - order of parameters is confusing, but it is probably too late
   to change now, should be (port, val) - destination on the left side,
   as in memset and C assignment operators.  */

#define __outb(val, port)			\
	__asm__ __volatile__ (			\
		"out %1,%0"			\
		: /* no outputs */		\
		: "r" ((uint8_t)(val)),		\
		  "M" ((uint8_t)((port)-__SFR_OFFSET))	\
	)

#define __outb_zero(port)			\
	__asm__ __volatile__ (			\
		"out %0,__zero_reg__"		\
		: /* no outputs */		\
		: "M" ((uint8_t)((port)-__SFR_OFFSET))	\
	)

/* Macros for access to 8-bit I/O registers (auto detect const address).  */

#define __port_in_out_ok(port, size) \
  (__builtin_constant_p(port) && (port) <= 0x40 + __SFR_OFFSET - (size))

#define inp(port) \
  (__port_in_out_ok((port), 1) ? __inb((port)) : __mmio((port)))

#define outp(val,port) do {				\
	if (__port_in_out_ok((port), 1)) {		\
		if (__builtin_constant_p((val)) && ((val) == 0)) \
			__outb_zero((port));		\
		else					\
			__outb((val), (port));		\
	} else						\
		__mmio((port)) = (val);			\
 } while (0)

/* Macros for access to 16-bit I/O registers (ADC, ICR1, OCR1, TCNT1) -
   read low byte first, then high byte (from the TEMP register),
   write the high byte first (to the TEMP register), then the low byte.
   If used from interrupts as well as from the main program, disable
   interrupts first, or use the *_atomic versions (avoid interrupts
   in the middle of operation - there is only one TEMP register).  */

#define __inw(port) ({					\
	uint16_t __t;					\
	__asm__ __volatile__ (				\
		"in %A0,%1" "\n\t"			\
		"in %B0,(%1)+1"				\
		: "=r" (__t)				\
		: "M" ((uint8_t)((port)-__SFR_OFFSET))	\
	);						\
	__t;						\
 })

#define __inw_atomic(port) ({				\
	uint16_t __t;					\
	__asm__ __volatile__ (				\
		"in __tmp_reg__,__SREG__" "\n\t"	\
		"cli" "\n\t"				\
		"in %A0,%1" "\n\t"			\
		"out __SREG__,__tmp_reg__" "\n\t"	\
		"in %B0,(%1)+1"				\
		: "=r" (__t)				\
		: "M" ((uint8_t)((port)-__SFR_OFFSET))	\
		: "r0"					\
	);						\
	__t;						\
 })

#define __outw(val, port)				\
	__asm__ __volatile__ (				\
		"out (%1)+1,%B0" "\n\t"			\
		"out %1,%A0"				\
		: /* no outputs */			\
		: "r" ((uint16_t)(val)),		\
		  "M" ((uint8_t)((port)-__SFR_OFFSET))	\
	)

#define __outw_atomic(val, port)			\
	__asm__ __volatile__ (				\
		"in __tmp_reg__,__SREG__" "\n\t"	\
		"cli" "\n\t"				\
		"out (%1)+1,%B0" "\n\t"			\
		"out __SREG__,__tmp_reg__" "\n\t"	\
		"out %1,%A0"				\
		: /* no outputs */			\
		: "r" ((uint16_t)(val)),		\
		  "M" ((uint8_t)((port)-__SFR_OFFSET))	\
		: "r0"					\
	)

/* __cbi / __sbi require constant port-__SFR_OFFSET < 0x20 and constant bit */

#define __cbi(port, bit)				\
	__asm__ __volatile__ (				\
		"cbi %0,%1"				\
		: /* no outputs */			\
		: "I" ((uint8_t)((port)-__SFR_OFFSET)),	\
		  "I" ((uint8_t)(bit))			\
	)

#define __sbi(port, bit)				\
	__asm__ __volatile__ (				\
		"sbi %0,%1"				\
		: /* no outputs */			\
		: "I" ((uint8_t)((port)-__SFR_OFFSET)),	\
		  "I" ((uint8_t)(bit))			\
	)

/* __port_and / __port_or work with any constant port */

#define __port_and(port, val)				\
	__asm__ __volatile__ (				\
		"in __tmp_reg__,%0" "\n\t"		\
		"and __tmp_reg__,%1" "\n\t"		\
		"out %0,__tmp_reg__"			\
		: /* no outputs */			\
		: "I" ((uint8_t)((port)-__SFR_OFFSET)),	\
		  "r" ((uint8_t)(val))			\
		: "r0"					\
	)

#define __port_or(port, val)				\
	__asm__ __volatile__ (				\
		"in __tmp_reg__,%0" "\n\t"		\
		"or __tmp_reg__,%1" "\n\t"		\
		"out %0,__tmp_reg__"			\
		: /* no outputs */			\
		: "I" ((uint8_t)((port)-__SFR_OFFSET)),	\
		  "r" ((uint8_t)(val))			\
		: "r0"					\
	)

/* __cbi_const_port / __sbi_const_port work with const or non-const bit */

#define __port_cbi_sbi_ok(port, bit)				\
  (__builtin_constant_p(port) && (port) < 0x20 + __SFR_OFFSET)	\
   && __builtin_constant_p(bit))

#define __cbi_const_port(port, bit) do {		\
	if (__port_cbi_sbi_ok((port), (bit))		\
		__cbi((port), (bit));			\
	else						\
		__port_and((port), ~BV((bit)));		\
 } while (0)

#define __sbi_const_port(port, bit) do {		\
	if (__port_cbi_sbi_ok((port), (bit))		\
		__sbi((port), (bit));			\
	else						\
		__port_or((port), BV((bit)));		\
 } while (0)

#endif /* _IOMACROS_H_ */
