| /* This file is part of the program psim. | |
| Copyright (C) 1994-1996, Andrew Cagney <cagney@highland.com.au> | |
| 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 3 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, see <http://www.gnu.org/licenses/>. | |
| */ | |
| #ifndef _HW_NVRAM_C_ | |
| #define _HW_NVRAM_C_ | |
| #ifndef STATIC_INLINE_HW_NVRAM | |
| #define STATIC_INLINE_HW_NVRAM STATIC_INLINE | |
| #endif | |
| #include "device_table.h" | |
| #include <time.h> | |
| #include <string.h> | |
| /* DEVICE | |
| nvram - non-volatile memory with clock | |
| DESCRIPTION | |
| This device implements a small byte addressable non-volatile | |
| memory. The top 8 bytes of this memory include a real-time clock. | |
| PROPERTIES | |
| reg = <address> <size> (required) | |
| Specify the address/size of this device within its parents address | |
| space. | |
| timezone = <integer> (optional) | |
| Adjustment to the hosts current GMT (in seconds) that should be | |
| applied when updating the NVRAM's clock. If no timezone is | |
| specified, zero (GMT or UCT) is assumed. | |
| */ | |
| typedef struct _hw_nvram_device { | |
| uint8_t *memory; | |
| unsigned sizeof_memory; | |
| time_t host_time; | |
| unsigned timezone; | |
| /* useful */ | |
| unsigned addr_year; | |
| unsigned addr_month; | |
| unsigned addr_date; | |
| unsigned addr_day; | |
| unsigned addr_hour; | |
| unsigned addr_minutes; | |
| unsigned addr_seconds; | |
| unsigned addr_control; | |
| } hw_nvram_device; | |
| static void * | |
| hw_nvram_create(const char *name, | |
| const device_unit *unit_address, | |
| const char *args) | |
| { | |
| hw_nvram_device *nvram = ZALLOC(hw_nvram_device); | |
| return nvram; | |
| } | |
| typedef struct _hw_nvram_reg_spec { | |
| uint32_t base; | |
| uint32_t size; | |
| } hw_nvram_reg_spec; | |
| static void | |
| hw_nvram_init_address(device *me) | |
| { | |
| hw_nvram_device *nvram = (hw_nvram_device*)device_data(me); | |
| /* use the generic init code to attach this device to its parent bus */ | |
| generic_device_init_address(me); | |
| /* find the first non zero reg property and use that as the device | |
| size */ | |
| if (nvram->sizeof_memory == 0) { | |
| reg_property_spec reg; | |
| int reg_nr; | |
| for (reg_nr = 0; | |
| device_find_reg_array_property(me, "reg", reg_nr, ®); | |
| reg_nr++) { | |
| unsigned attach_size; | |
| if (device_size_to_attach_size(device_parent(me), | |
| ®.size, &attach_size, | |
| me)) { | |
| nvram->sizeof_memory = attach_size; | |
| break; | |
| } | |
| } | |
| if (nvram->sizeof_memory == 0) | |
| device_error(me, "reg property must contain a non-zero phys-addr:size tupple"); | |
| if (nvram->sizeof_memory < 8) | |
| device_error(me, "NVRAM must be at least 8 bytes in size"); | |
| } | |
| /* initialize the hw_nvram */ | |
| if (nvram->memory == NULL) { | |
| nvram->memory = zalloc(nvram->sizeof_memory); | |
| } | |
| else | |
| memset(nvram->memory, 0, nvram->sizeof_memory); | |
| if (device_find_property(me, "timezone") == NULL) | |
| nvram->timezone = 0; | |
| else | |
| nvram->timezone = device_find_integer_property(me, "timezone"); | |
| nvram->addr_year = nvram->sizeof_memory - 1; | |
| nvram->addr_month = nvram->sizeof_memory - 2; | |
| nvram->addr_date = nvram->sizeof_memory - 3; | |
| nvram->addr_day = nvram->sizeof_memory - 4; | |
| nvram->addr_hour = nvram->sizeof_memory - 5; | |
| nvram->addr_minutes = nvram->sizeof_memory - 6; | |
| nvram->addr_seconds = nvram->sizeof_memory - 7; | |
| nvram->addr_control = nvram->sizeof_memory - 8; | |
| } | |
| static int | |
| hw_nvram_bcd(int val) | |
| { | |
| val = val % 100; | |
| if (val < 0) | |
| val += 100; | |
| return ((val / 10) << 4) + (val % 10); | |
| } | |
| /* If reached an update interval and allowed, update the clock within | |
| the hw_nvram. While this function could be implemented using events | |
| it isn't on the assumption that the HW_NVRAM will hardly ever be | |
| referenced and hence there is little need in keeping the clock | |
| continually up-to-date */ | |
| static void | |
| hw_nvram_update_clock(hw_nvram_device *nvram, | |
| cpu *processor) | |
| { | |
| if (!(nvram->memory[nvram->addr_control] & 0xc0)) { | |
| time_t host_time = time(NULL); | |
| if (nvram->host_time != host_time) { | |
| time_t nvtime = host_time + nvram->timezone; | |
| struct tm *clock = gmtime(&nvtime); | |
| nvram->host_time = host_time; | |
| nvram->memory[nvram->addr_year] = hw_nvram_bcd(clock->tm_year); | |
| nvram->memory[nvram->addr_month] = hw_nvram_bcd(clock->tm_mon + 1); | |
| nvram->memory[nvram->addr_date] = hw_nvram_bcd(clock->tm_mday); | |
| nvram->memory[nvram->addr_day] = hw_nvram_bcd(clock->tm_wday + 1); | |
| nvram->memory[nvram->addr_hour] = hw_nvram_bcd(clock->tm_hour); | |
| nvram->memory[nvram->addr_minutes] = hw_nvram_bcd(clock->tm_min); | |
| nvram->memory[nvram->addr_seconds] = hw_nvram_bcd(clock->tm_sec); | |
| } | |
| } | |
| } | |
| static void | |
| hw_nvram_set_clock(hw_nvram_device *nvram, cpu *processor) | |
| { | |
| error ("fixme - how do I set the localtime\n"); | |
| } | |
| static unsigned | |
| hw_nvram_io_read_buffer(device *me, | |
| void *dest, | |
| int space, | |
| unsigned_word addr, | |
| unsigned nr_bytes, | |
| cpu *processor, | |
| unsigned_word cia) | |
| { | |
| int i; | |
| hw_nvram_device *nvram = (hw_nvram_device*)device_data(me); | |
| for (i = 0; i < nr_bytes; i++) { | |
| unsigned address = (addr + i) % nvram->sizeof_memory; | |
| uint8_t data = nvram->memory[address]; | |
| hw_nvram_update_clock(nvram, processor); | |
| ((uint8_t*)dest)[i] = data; | |
| } | |
| return nr_bytes; | |
| } | |
| static unsigned | |
| hw_nvram_io_write_buffer(device *me, | |
| const void *source, | |
| int space, | |
| unsigned_word addr, | |
| unsigned nr_bytes, | |
| cpu *processor, | |
| unsigned_word cia) | |
| { | |
| int i; | |
| hw_nvram_device *nvram = (hw_nvram_device*)device_data(me); | |
| for (i = 0; i < nr_bytes; i++) { | |
| unsigned address = (addr + i) % nvram->sizeof_memory; | |
| uint8_t data = ((uint8_t*)source)[i]; | |
| if (address == nvram->addr_control | |
| && (data & 0x80) == 0 | |
| && (nvram->memory[address] & 0x80) == 0x80) | |
| hw_nvram_set_clock(nvram, processor); | |
| else | |
| hw_nvram_update_clock(nvram, processor); | |
| nvram->memory[address] = data; | |
| } | |
| return nr_bytes; | |
| } | |
| static device_callbacks const hw_nvram_callbacks = { | |
| { hw_nvram_init_address, }, | |
| { NULL, }, /* address */ | |
| { hw_nvram_io_read_buffer, hw_nvram_io_write_buffer }, /* IO */ | |
| }; | |
| const device_descriptor hw_nvram_device_descriptor[] = { | |
| { "nvram", hw_nvram_create, &hw_nvram_callbacks }, | |
| { NULL }, | |
| }; | |
| #endif /* _HW_NVRAM_C_ */ |