aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-s3c2410/gpio.c
blob: db6393c9986091328cd1f31b00beeb1219d42315 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* linux/arch/arm/mach-s3c2410/gpio.c
 *
 * Copyright (c) 2004-2005 Simtec Electronics
 *	Ben Dooks <ben@simtec.co.uk>
 *
 * S3C2410 GPIO support
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>

#include <asm/hardware.h>
#include <asm/irq.h>
#include <asm/io.h>

#include <asm/arch/regs-gpio.h>

void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long mask;
	unsigned long con;
	unsigned long flags;

	if (pin < S3C2410_GPIO_BANKB) {
		mask = 1 << S3C2410_GPIO_OFFSET(pin);
	} else {
		mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
	}

	switch (function) {
	case S3C2410_GPIO_LEAVE:
		mask = 0;
		function = 0;
		break;

	case S3C2410_GPIO_INPUT:
	case S3C2410_GPIO_OUTPUT:
	case S3C2410_GPIO_SFN2:
	case S3C2410_GPIO_SFN3:
		if (pin < S3C2410_GPIO_BANKB) {
			function &= 1;
			function <<= S3C2410_GPIO_OFFSET(pin);
		} else {
			function &= 3;
			function <<= S3C2410_GPIO_OFFSET(pin)*2;
		}
	}

	/* modify the specified register wwith IRQs off */

	local_irq_save(flags);

	con  = __raw_readl(base + 0x00);
	con &= ~mask;
	con |= function;

	__raw_writel(con, base + 0x00);

	local_irq_restore(flags);
}

EXPORT_SYMBOL(s3c2410_gpio_cfgpin);

unsigned int s3c2410_gpio_getcfg(unsigned int pin)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long mask;

	if (pin < S3C2410_GPIO_BANKB) {
		mask = 1 << S3C2410_GPIO_OFFSET(pin);
	} else {
		mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
	}

	return __raw_readl(base) & mask;
}

EXPORT_SYMBOL(s3c2410_gpio_getcfg);

void s3c2410_gpio_pullup(unsigned int pin, unsigned int to)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long offs = S3C2410_GPIO_OFFSET(pin);
	unsigned long flags;
	unsigned long up;

	if (pin < S3C2410_GPIO_BANKB)
		return;

	local_irq_save(flags);

	up = __raw_readl(base + 0x08);
	up &= ~(1L << offs);
	up |= to << offs;
	__raw_writel(up, base + 0x08);

	local_irq_restore(flags);
}

EXPORT_SYMBOL(s3c2410_gpio_pullup);

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long offs = S3C2410_GPIO_OFFSET(pin);
	unsigned long flags;
	unsigned long dat;

	local_irq_save(flags);

	dat = __raw_readl(base + 0x04);
	dat &= ~(1 << offs);
	dat |= to << offs;
	__raw_writel(dat, base + 0x04);

	local_irq_restore(flags);
}

EXPORT_SYMBOL(s3c2410_gpio_setpin);

unsigned int s3c2410_gpio_getpin(unsigned int pin)
{
	void __iomem *base = S3C24XX_GPIO_BASE(pin);
	unsigned long offs = S3C2410_GPIO_OFFSET(pin);

	return __raw_readl(base + 0x04) & (1<< offs);
}

EXPORT_SYMBOL(s3c2410_gpio_getpin);

unsigned int s3c2410_modify_misccr(unsigned int clear, unsigned int change)
{
	unsigned long flags;
	unsigned long misccr;

	local_irq_save(flags);
	misccr = __raw_readl(S3C24XX_MISCCR);
	misccr &= ~clear;
	misccr ^= change;
	__raw_writel(misccr, S3C24XX_MISCCR);
	local_irq_restore(flags);

	return misccr;
}

EXPORT_SYMBOL(s3c2410_modify_misccr);
class="hl com"> * - The work queue thread resumes and calls the free function for the element * it started cleaning up earlier. In the process it decrements the * filestreams counter for an AG that now has no references. * * With a shrinkfs feature, the above scenario could panic the system. * * All other uses of the following macros should be protected by either the * m_peraglock held in read mode, or the cache's internal locking exposed by the * interval between a call to xfs_mru_cache_lookup() and a call to * xfs_mru_cache_done(). In addition, the m_peraglock must be held in read mode * when new elements are added to the cache. * * Combined, these locking rules ensure that no associations will ever exist in * the cache that reference per-ag array elements that have since been * reallocated. */ int xfs_filestream_peek_ag( xfs_mount_t *mp, xfs_agnumber_t agno) { struct xfs_perag *pag; int ret; pag = xfs_perag_get(mp, agno); ret = atomic_read(&pag->pagf_fstrms); xfs_perag_put(pag); return ret; } static int xfs_filestream_get_ag( xfs_mount_t *mp, xfs_agnumber_t agno) { struct xfs_perag *pag; int ret; pag = xfs_perag_get(mp, agno); ret = atomic_inc_return(&pag->pagf_fstrms); xfs_perag_put(pag); return ret; } static void xfs_filestream_put_ag( xfs_mount_t *mp, xfs_agnumber_t agno) { struct xfs_perag *pag; pag = xfs_perag_get(mp, agno); atomic_dec(&pag->pagf_fstrms); xfs_perag_put(pag); } static void xfs_fstrm_free_func( struct xfs_mru_cache_elem *mru) { struct xfs_fstrm_item *item = container_of(mru, struct xfs_fstrm_item, mru); xfs_filestream_put_ag(item->ip->i_mount, item->ag); trace_xfs_filestream_free(item->ip, item->ag); kmem_free(item); } /* * Scan the AGs starting at startag looking for an AG that isn't in use and has * at least minlen blocks free. */ static int xfs_filestream_pick_ag( struct xfs_inode *ip, xfs_agnumber_t startag, xfs_agnumber_t *agp, int flags, xfs_extlen_t minlen) { struct xfs_mount *mp = ip->i_mount; struct xfs_fstrm_item *item; struct xfs_perag *pag; xfs_extlen_t longest, free = 0, minfree, maxfree = 0; xfs_agnumber_t ag, max_ag = NULLAGNUMBER; int err, trylock, nscan; ASSERT(S_ISDIR(ip->i_d.di_mode)); /* 2% of an AG's blocks must be free for it to be chosen. */ minfree = mp->m_sb.sb_agblocks / 50; ag = startag; *agp = NULLAGNUMBER; /* For the first pass, don't sleep trying to init the per-AG. */ trylock = XFS_ALLOC_FLAG_TRYLOCK; for (nscan = 0; 1; nscan++) { trace_xfs_filestream_scan(ip, ag); pag = xfs_perag_get(mp, ag); if (!pag->pagf_init) { err = xfs_alloc_pagf_init(mp, NULL, ag, trylock); if (err && !trylock) { xfs_perag_put(pag); return err; } } /* Might fail sometimes during the 1st pass with trylock set. */ if (!pag->pagf_init) goto next_ag; /* Keep track of the AG with the most free blocks. */ if (pag->pagf_freeblks > maxfree) { maxfree = pag->pagf_freeblks; max_ag = ag; } /* * The AG reference count does two things: it enforces mutual * exclusion when examining the suitability of an AG in this * loop, and it guards against two filestreams being established * in the same AG as each other. */ if (xfs_filestream_get_ag(mp, ag) > 1) { xfs_filestream_put_ag(mp, ag); goto next_ag; } longest = xfs_alloc_longest_free_extent(mp, pag, xfs_alloc_min_freelist(mp, pag)); if (((minlen && longest >= minlen) || (!minlen && pag->pagf_freeblks >= minfree)) && (!pag->pagf_metadata || !(flags & XFS_PICK_USERDATA) || (flags & XFS_PICK_LOWSPACE))) { /* Break out, retaining the reference on the AG. */ free = pag->pagf_freeblks; xfs_perag_put(pag); *agp = ag; break; } /* Drop the reference on this AG, it's not usable. */ xfs_filestream_put_ag(mp, ag); next_ag: xfs_perag_put(pag); /* Move to the next AG, wrapping to AG 0 if necessary. */ if (++ag >= mp->m_sb.sb_agcount) ag = 0; /* If a full pass of the AGs hasn't been done yet, continue. */ if (ag != startag) continue; /* Allow sleeping in xfs_alloc_pagf_init() on the 2nd pass. */ if (trylock != 0) { trylock = 0; continue; } /* Finally, if lowspace wasn't set, set it for the 3rd pass. */ if (!(flags & XFS_PICK_LOWSPACE)) { flags |= XFS_PICK_LOWSPACE; continue; } /* * Take the AG with the most free space, regardless of whether * it's already in use by another filestream. */ if (max_ag != NULLAGNUMBER) { xfs_filestream_get_ag(mp, max_ag); free = maxfree; *agp = max_ag; break; } /* take AG 0 if none matched */ trace_xfs_filestream_pick(ip, *agp, free, nscan); *agp = 0; return 0; } trace_xfs_filestream_pick(ip, *agp, free, nscan); if (*agp == NULLAGNUMBER) return 0; err = -ENOMEM; item = kmem_alloc(sizeof(*item), KM_MAYFAIL); if (!item) goto out_put_ag; item->ag = *agp; item->ip = ip; err = xfs_mru_cache_insert(mp->m_filestream, ip->i_ino, &item->mru); if (err) { if (err == -EEXIST) err = 0; goto out_free_item; } return 0; out_free_item: kmem_free(item); out_put_ag: xfs_filestream_put_ag(mp, *agp); return err; } static struct xfs_inode * xfs_filestream_get_parent( struct xfs_inode *ip) { struct inode *inode = VFS_I(ip), *dir = NULL; struct dentry *dentry, *parent; dentry = d_find_alias(inode); if (!dentry) goto out; parent = dget_parent(dentry); if (!parent) goto out_dput; dir = igrab(d_inode(parent)); dput(parent); out_dput: dput(dentry); out: return dir ? XFS_I(dir) : NULL; } /* * Find the right allocation group for a file, either by finding an * existing file stream or creating a new one. * * Returns NULLAGNUMBER in case of an error. */ xfs_agnumber_t xfs_filestream_lookup_ag( struct xfs_inode *ip) { struct xfs_mount *mp = ip->i_mount; struct xfs_inode *pip = NULL; xfs_agnumber_t startag, ag = NULLAGNUMBER; struct xfs_mru_cache_elem *mru; ASSERT(S_ISREG(ip->i_d.di_mode)); pip = xfs_filestream_get_parent(ip); if (!pip) return NULLAGNUMBER; mru = xfs_mru_cache_lookup(mp->m_filestream, pip->i_ino); if (mru) { ag = container_of(mru, struct xfs_fstrm_item, mru)->ag; xfs_mru_cache_done(mp->m_filestream); trace_xfs_filestream_lookup(ip, ag); goto out; } /* * Set the starting AG using the rotor for inode32, otherwise * use the directory inode's AG. */ if (mp->m_flags & XFS_MOUNT_32BITINODES) { xfs_agnumber_t rotorstep = xfs_rotorstep; startag = (mp->m_agfrotor / rotorstep) % mp->m_sb.sb_agcount; mp->m_agfrotor = (mp->m_agfrotor + 1) % (mp->m_sb.sb_agcount * rotorstep); } else startag = XFS_INO_TO_AGNO(mp, pip->i_ino); if (xfs_filestream_pick_ag(pip, startag, &ag, 0, 0)) ag = NULLAGNUMBER; out: IRELE(pip); return ag; } /* * Pick a new allocation group for the current file and its file stream. * * This is called when the allocator can't find a suitable extent in the * current AG, and we have to move the stream into a new AG with more space. */ int xfs_filestream_new_ag( struct xfs_bmalloca *ap, xfs_agnumber_t *agp) { struct xfs_inode *ip = ap->ip, *pip; struct xfs_mount *mp = ip->i_mount; xfs_extlen_t minlen = ap->length; xfs_agnumber_t startag = 0; int flags, err = 0; struct xfs_mru_cache_elem *mru; *agp = NULLAGNUMBER; pip = xfs_filestream_get_parent(ip); if (!pip) goto exit; mru = xfs_mru_cache_remove(mp->m_filestream, pip->i_ino); if (mru) { struct xfs_fstrm_item *item = container_of(mru, struct xfs_fstrm_item, mru); startag = (item->ag + 1) % mp->m_sb.sb_agcount; } flags = (ap->userdata ? XFS_PICK_USERDATA : 0) | (ap->flist->xbf_low ? XFS_PICK_LOWSPACE : 0); err = xfs_filestream_pick_ag(pip, startag, agp, flags, minlen); /* * Only free the item here so we skip over the old AG earlier. */ if (mru) xfs_fstrm_free_func(mru); IRELE(pip); exit: if (*agp == NULLAGNUMBER) *agp = 0; return err; } void xfs_filestream_deassociate( struct xfs_inode *ip) { xfs_mru_cache_delete(ip->i_mount->m_filestream, ip->i_ino); } int xfs_filestream_mount( xfs_mount_t *mp) { /* * The filestream timer tunable is currently fixed within the range of * one second to four minutes, with five seconds being the default. The