summaryrefslogtreecommitdiff
path: root/cleopatre/linux-2.6.25.10-spc300/arch/arm/mach-spc300/spc300.c
blob: 35f9eace5a3a79c4d3d70224fa9a2c6bccb464c0 (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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
/*
 * 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 <linux/proc_fs.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>
#ifdef CONFIG_CHIP_FEATURE_SRAM
#include <asm/arch/ips/sram.h>
#endif
#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);

#ifdef CONFIG_PM
static int spc300_set_wake(unsigned int irq, unsigned int on);
#endif

static struct irq_chip spc300_irq_chip = {
    .name   = "SPC300 GIC",
    .ack    = spc300_mask_irq,
    .mask   = spc300_mask_irq,
    .unmask = spc300_unmask_irq,
#ifdef CONFIG_PM
    .set_wake = spc300_set_wake,
#endif
};

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(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 },
    { IO_ADDRESS(MIU_BASE)           , __phys_to_pfn(MIU_BASE)           , SZ_8K          , 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 },
#ifdef CONFIG_CHIP_FEATURE_SRAM
    { IO_ADDRESS(SRAM_BASE)          , __phys_to_pfn(SRAM_BASE)          , SRAM_SIZE      , MT_DEVICE },
#endif
};
static struct map_desc spc300_nvram_io_desc;
static struct map_desc spc300_plccode_io_desc;

uint32_t nvram_offset = NVRAM_OFFSET_INVALID;

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

/** Global DSP mode */
spc300_atag_dsp_mode_t spc300_dsp_mode;
EXPORT_SYMBOL(spc300_dsp_mode);

/** 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);

/** Current image slot number. */
static int spc300_cur_img_slot = -1;

/** Autoswitch enable. */
int spc300_autoswitch_en = 0;

/** Wakeup interrupts mask. */
static uint32_t spc300_wakeup_irq_mask;

/** Backup of interrupts mask at suspend. */
static uint32_t spc300_backup_irq_mask;

/** Spidimg proc dir pointer. */
struct proc_dir_entry *spc300_spidimg_proc_dir;
EXPORT_SYMBOL(spc300_spidimg_proc_dir);

/**
 * 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);
}

#ifdef CONFIG_PM

/**
 * Enable/disable the use of an interrupt to wake system up.
 * \param  irq  irq number.
 * \param  on  enable/disable.
 * \return  error code.
 */
static int spc300_set_wake(unsigned int irq, unsigned int on)
{
    if (unlikely(irq >= NR_IRQS))
        return -EINVAL;

    if (on)
        spc300_wakeup_irq_mask |= (1 << irq);
    else
        spc300_wakeup_irq_mask &= ~(1 << irq);

    return 0;
}

/**
 * Set interrupts mask for suspend mode, only enable interrupts which should
 * wake system up.
 */
void spc300_irq_suspend(void)
{
    spc300_backup_irq_mask = IRQ_INTEN_VA;
    IRQ_INTEN_VA = spc300_wakeup_irq_mask;
}

/**
 * Restore interrupts after spc300_irq_suspend has been called.
 */
void spc300_irq_resume(void)
{
    IRQ_INTEN_VA = spc300_backup_irq_mask;
}

#endif /* CONFIG_PM */

/**
 * 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);
#ifdef CONFIG_CHIP_FEATURE_ETHERNET2
    set_irq_priority(INT_ETH2, INT_PRIO_ETH);
#endif
    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_start(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)
{
    //Find bootloader informations
    for( ; tags->hdr.size ; tags = tag_next(tags))
    {
        if(tags->hdr.tag == ATAG_SPC300)
        {
            nvram_offset = tags->u.spc300.nvram_offset;
            spc300_plc_mem_size = tags->u.spc300.plc_mem_size;
            /* We assume that any element added to ATAG will never ever be
             * removed. */
            if (tags->hdr.size >= tag_size(tag_spc300))
            {
                spc300_cur_img_slot = tags->u.spc300.cur_img_slot;
                spc300_autoswitch_en = tags->u.spc300.autoswitch_en;
                spc300_dsp_mode = tags->u.spc300.dsp_mode;
            }
            tags->hdr.tag = ATAG_NONE;
            tags->hdr.size = 0;
        }
        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;
}

/**
 * Read the current image slot by /proc.
 *
 * \param  file  file structure.
 * \param  buffer  string pointer given by user.
 * \param  start  string pointer begin.
 * \param  offset  offset value.
 * \param  count  count parameter.
 * \param  eof  end of file.
 * \param  data  current image slot.
 * \return  new pointer position.
 */
static int spc300_readproc_img_slot (char *buf, char **start, off_t offset,
                                     int count, int *eof, void *data)
{
    int img_slot = *(int *)data;
    char *p;

    p = buf;

    p += sprintf(p, "%d\n", img_slot);

    *eof = 1;

    return p-buf+1;
}

static void spc300_spidimg_proc_init (void)
{
    struct proc_dir_entry *entry;

    spc300_spidimg_proc_dir = proc_mkdir("spidimg", &proc_root);

    /* This entry is read only, so there is no write function and permissions
     * are set to 0444. */
    entry = create_proc_entry("current_img_slot", 0, spc300_spidimg_proc_dir);
    entry->read_proc  = spc300_readproc_img_slot;
    entry->mode       = S_IRUGO;
    entry->data       = (void *)(&spc300_cur_img_slot);
}

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