/*
 * safe read and write memory routines callable while atomic
 *
 * Copyright 2012 Imagination Technologies
 */

#include <linux/uaccess.h>
#include <asm/io.h>

/*
 * The generic probe_kernel_write() uses the user copy code which can split the
 * writes if the source is unaligned, and repeats writes to make exceptions
 * precise. We override it here to avoid these things happening to memory mapped
 * IO memory where they could have undesired effects.
 * Due to the use of CACHERD instruction this only works on Meta2 onwards.
 */
#ifdef CONFIG_METAG_META21
long probe_kernel_write(void *dst, const void *src, size_t size)
{
	unsigned long ldst = (unsigned long)dst;
	void __iomem *iodst = (void __iomem *)dst;
	unsigned long lsrc = (unsigned long)src;
	const u8 *psrc = (u8 *)src;
	unsigned int pte, i;
	u8 bounce[8] __aligned(8);

	if (!size)
		return 0;

	/* Use the write combine bit to decide is the destination is MMIO. */
	pte = __builtin_meta2_cacherd(dst);

	/* Check the mapping is valid and writeable. */
	if ((pte & (MMCU_ENTRY_WR_BIT | MMCU_ENTRY_VAL_BIT))
	    != (MMCU_ENTRY_WR_BIT | MMCU_ENTRY_VAL_BIT))
		return -EFAULT;

	/* Fall back to generic version for cases we're not interested in. */
	if (pte & MMCU_ENTRY_WRC_BIT	|| /* write combined memory */
	    (ldst & (size - 1))		|| /* destination unaligned */
	    size > 8			|| /* more than max write size */
	    (size & (size - 1)))	   /* non power of 2 size */
		return __probe_kernel_write(dst, src, size);

	/* If src is unaligned, copy to the aligned bounce buffer first. */
	if (lsrc & (size - 1)) {
		for (i = 0; i < size; ++i)
			bounce[i] = psrc[i];
		psrc = bounce;
	}

	switch (size) {
	case 1:
		writeb(*psrc, iodst);
		break;
	case 2:
		writew(*(const u16 *)psrc, iodst);
		break;
	case 4:
		writel(*(const u32 *)psrc, iodst);
		break;
	case 8:
		writeq(*(const u64 *)psrc, iodst);
		break;
	}
	return 0;
}
#endif