summaryrefslogtreecommitdiff
path: root/cleopatre/linux-2.6.25.10-spc300/arch/arm/mach-spc300/spc300.c
blob: dc7c0484987342e13c2c2daf89c9dc77fc72448a (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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*
 * arch/arm/mach-spc300/spc300.c
 *
 * (C) Copyright 2009 SPiDCOM Technologies
 *
 * 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/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>

#include <asm/hardware.h>
#include <asm/setup.h>
#include <asm/irq.h>
#include <asm/mach/irq.h>
#include <asm/mach/time.h>
#include <asm/mach/map.h>

#include <asm/arch/ips/timer.h>
#include <asm/arch/ips/gic.h>
#include <asm/arch/wdt.h>

#include <asm/arch/nvram.h>

#include "spc300.h"
#include "spc300-reset_cause.h"

#define SYSTEMTICK (1000000/HZ)

static void spc300_mask_irq(unsigned int irq);
static void spc300_unmask_irq(unsigned int irq);
static void set_irq_priority(uint32_t line, uint32_t priority);
static irqreturn_t spc300_timer_interrupt(int irq, void *dev_id);
static unsigned long spc300_gettimeoffset(void);
static void __init spc300_init_time(void);

static struct irq_chip spc300_irq_chip = {
    .name   = "SPC300 GIC",
    .ack    = spc300_mask_irq,
    .mask   = spc300_mask_irq,
    .unmask = spc300_unmask_irq,
};

static struct irqaction spc300_timer_irq = {
    .name    = "SPC300 Tick Timer",
    .flags   = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
    .handler = spc300_timer_interrupt
};

struct sys_timer spc300_timer = {
    .init   = spc300_init_time,
    .offset = spc300_gettimeoffset
};

static struct map_desc spc300_io_desc[] __initdata = {
/*    virtual                          physical                            length           domain */
    { IO_ADDRESS(ARM_ICTL_BASE)      , __phys_to_pfn(ARM_ICTL_BASE)      , SZ_1K          , MT_DEVICE },
    { IO_ADDRESS(ARM_TIMER1_BASE)    , __phys_to_pfn(ARM_TIMER1_BASE)    , SZ_1K          , MT_DEVICE },
    { IO_ADDRESS(ARM_UART1_BASE)     , __phys_to_pfn(ARM_UART1_BASE)     , SZ_1K          , MT_DEVICE },
    { IO_ADDRESS(ARM_UART2_BASE)     , __phys_to_pfn(ARM_UART2_BASE)     , SZ_1K          , MT_DEVICE },
    { IO_ADDRESS(SPI_BASE)           , __phys_to_pfn(SPI_BASE)           , SZ_1K          , MT_DEVICE },
    { IO_ADDRESS(MARIA_REGBANK_BASE) , __phys_to_pfn(MARIA_REGBANK_BASE) , SZ_4K          , MT_DEVICE },
#ifdef CONFIG_CHIP_FEATURE_MIU_CTRL
    { IO_ADDRESS(AHB2MIU_BASE)       , __phys_to_pfn(AHB2MIU_BASE)       , SZ_1K          , MT_DEVICE },
#endif
    { IO_ADDRESS(ARM_GPIO_BASE)      , __phys_to_pfn(ARM_GPIO_BASE)      , SZ_1K          , MT_DEVICE },
    { IO_ADDRESS(ARM_WDT_BASE)       , __phys_to_pfn(ARM_WDT_BASE)       , SZ_1K          , MT_DEVICE }
};
static struct map_desc spc300_nvram_io_desc;
static struct map_desc spc300_plccode_io_desc;

/** Global NVRAM address */
spidcom_nvram_t spidcom_nvram;
EXPORT_SYMBOL(spidcom_nvram);

/** Physical address of the start of the memory dedicated to PLC. */
uint32_t spc300_plc_mem_start = 0;
EXPORT_SYMBOL(spc300_plc_mem_start);

/** Size of the memory dedicated to PLC. */
uint32_t spc300_plc_mem_size = 0;
EXPORT_SYMBOL(spc300_plc_mem_size);

/**
 * Mask IRQ line.
 *
 * \param  irq  irq number.
 */
static void spc300_mask_irq(unsigned int irq)
{
    IRQ_DISABLE (irq);
}

/**
 * Unmask IRQ line.
 *
 * \param  irq  irq number.
 */
static void spc300_unmask_irq(unsigned int irq)
{
    IRQ_ENABLE (irq);
}

/**
 * Set spc300 GIC priority level for each interrupt line.
 *
 * \param  line  irq number.
 * \param  priority priority level (0 min, 15 max).
 */
static void set_irq_priority(uint32_t line, uint32_t priority)
{
    volatile uint32_t *gic_base_prio_reg = IRQ_PRIO_ADDR_VA;
    if ((line < NR_IRQS) && (priority <= 15))
        *(gic_base_prio_reg + line) = priority;
}

/**
 * Initialize spc300 GIC.
 */
void spc300_init_irq(void)
{
    unsigned int i;

    //Mask all IRQs and FIQs
    IRQ_INTEN_VA = 0;
    FIQ_INTEN_VA = 0;

    //WARNING: All the priorities are hard-coded
    //the other are left to default
    for (i=0; i<NR_IRQS; i++)
        set_irq_priority(i, 0); //lowest priority
    set_irq_priority(INT_TIMER_1, INT_PRIO_TIMER_1);
    set_irq_priority(INT_UART_1, INT_PRIO_UART_1);
    set_irq_priority(INT_GPIO, INT_PRIO_GPIO);
    set_irq_priority(INT_ETH, INT_PRIO_ETH);
    set_irq_priority(INT_MBX, INT_PRIO_MBX);
    set_irq_priority(INT_MBX_ACK, INT_PRIO_MBX_ACK);

    //Define the priority level to the lowest priority
    IRQ_PLEVEL_VA = 0x0;

    //Configure each irq line to High Level triggered irqs
    for (i=0; i<NR_IRQS; i++)
    {
        set_irq_chip(i, &spc300_irq_chip);
        set_irq_handler(i, handle_level_irq);
        set_irq_flags(i, IRQF_VALID | IRQF_PROBE);
    }
}


/**
 * Tick timer interrupt handler.
 *
 * \param  irq  irq number.
 * \param  irq_handler  id device structure pointer.
 * \return  interrupt handler status.
 */
static irqreturn_t spc300_timer_interrupt(int irq, void *dev_id)
{
    volatile uint32_t dummy;

    //Refresh Watchdog if we used it on kernel mode
    if(!spc300_wdt_user_mode)
        spc300_wdt_refresh();

    //Check if it's our timer
    if(TIMER1INTSTAT_1_VA)
    {
        //Clear interrupt
        dummy = TIMER1EOI_1_VA;
        //Update the Linux tick
        timer_tick();
        return IRQ_HANDLED;
    }
    else
        return IRQ_NONE;
}

/**
 * spc300 time offset.
 *
 * \return  number of ms since last clock interrupt.
 */
static unsigned long spc300_gettimeoffset(void)
{
    volatile uint32_t timer_value;
    volatile uint32_t irq_pending;
    unsigned long us;

    //IRQs are disabled, check raw status to check if we've been interrupted
    do
    {
        irq_pending = TIMER1INTSTAT_1_VA;
        timer_value = TIMER1CURRENTVAL_1_VA;
    } while(irq_pending != TIMER1INTSTAT_1_VA); //Check if we've not been interrupted in the loop

#ifdef CONFIG_CHIP_SPC300ARIZONA
    us = (TIMER1LOADCOUNT_1_VA - timer_value) * (1000000 / TIMER_CLK); //for PCLK/200
#else
    us = ((TIMER1LOADCOUNT_1_VA - timer_value) * 1000) / (TIMER_CLK / 1000);
#endif

    if(irq_pending)
    {
        //We're in a stable state where an irq is pending:
        //seems like timer interrupts are masked or disabled
        us += SYSTEMTICK;
    }
    return us;
}

/**
 * Initialize spc300 tick timer.
 */
static void __init spc300_init_time(void)
{
    uint32_t dummy;

    //Restart Watchdog that is refreshed under timer interrupt
    spc300_wdt_restart(2); //2 seconds for timeout

    //Disable timer 1
    TIMER1CONTROLREG_1_VA = ~(TIMER_ENABLE_MASK | TIMER_MODE_MASK) & TIMER_INTMASK_MASK;

    //Clear timer1 interrupt
    dummy = TIMER1EOI_1_VA;

    //Configure timer1 for tick timer (auto-reload mode with IT)
    TIMER1CONTROLREG_1_VA = ~(TIMER_ENABLE_MASK | TIMER_INTMASK_MASK) & TIMER_MODE_MASK;

    //Load the starting value for a 10ms tick timer
#ifdef CONFIG_CHIP_SPC300ARIZONA
    TIMER1LOADCOUNT_1_VA = SYSTEMTICK*TIMER_CLK/1000000; //for PCLK/200
#else
    // T1_VAL = SYSTEMTICK*(TIMER_CLK/1000000)
    TIMER1LOADCOUNT_1_VA = (TIMER_CLK / HZ);
#endif

    //Setup irq
    setup_irq(INT_TIMER_1, &spc300_timer_irq);

    //Enable the timer1
    TIMER1CONTROLREG_1_VA |= TIMER_ENABLE_MASK;
}


/**
 * New mapping for IOs after MMU starting.
 */
void __init spc300_map_io(void)
{
    //Set general IO like Interrupt controller, tick timer...
    iotable_init(spc300_io_desc, ARRAY_SIZE(spc300_io_desc));

    //Set NVRAM mapping and copy it because direct access only accept word read
    iotable_init(&spc300_nvram_io_desc, 1);
    spidcom_nvram_copy((void*)&spidcom_nvram, (void*)spc300_nvram_io_desc.virtual, sizeof(spidcom_nvram_t));

    //Set PLC code mapping
    iotable_init(&spc300_plccode_io_desc, 1);
}

/**
 * Second spc300 initialisation,
 * just before memory_init,
 * to restore U-Boot parameters.
 *
 * \param  desc  machine descriptor.
 * \param  tags  tag.
 * \param  cmdline  command line.
 * \param  mi  memory informations.
 */
void spc300_fixup(struct machine_desc *desc, struct tag *tags, char **cmdline, struct meminfo *mi)
{
    uint32_t nvram_offset = 0;

    //Find bootloader informations
    for( ; tags->hdr.size ; tags = tag_next(tags))
    {
        if(tags->hdr.tag == ATAG_SPC300)
        {
            nvram_offset = tags->u.spc300.nvram_offset;
            tags->hdr.tag = ATAG_NONE;

            spc300_plc_mem_size = tags->u.spc300.plc_mem_size;
        }
        if(tags->hdr.tag == ATAG_MEM)
        {
            spc300_plc_mem_start = tags->u.mem.start + tags->u.mem.size;
        }
    }

    //Set NVRAM mapping
    spc300_nvram_io_desc.virtual = IO_ADDRESS(SPI_BASE_DIR) + nvram_offset;
    spc300_nvram_io_desc.pfn = __phys_to_pfn(SPI_BASE_DIR + nvram_offset);
    spc300_nvram_io_desc.length = sizeof(spidcom_nvram_t);
    spc300_nvram_io_desc.type = MT_DEVICE;

    //Set PLC code mapping
    spc300_plccode_io_desc.virtual = VIRT_PLCCODE_BASE;
    spc300_plccode_io_desc.pfn = __phys_to_pfn(spc300_plc_mem_start);
    spc300_plccode_io_desc.length = spc300_plc_mem_size;
    spc300_plccode_io_desc.type = MT_DEVICE;
}

/**
 * Initialize spc300.
 */
void spc300_init(void)
{
    spc300_reset_cause_init();
}