1/*  This file is part of the program psim.
2
3    Copyright (C) 1994-1996, Andrew Cagney <cagney@highland.com.au>
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19    */
20
21
22#ifndef _HW_NVRAM_C_
23#define _HW_NVRAM_C_
24
25#ifndef STATIC_INLINE_HW_NVRAM
26#define STATIC_INLINE_HW_NVRAM STATIC_INLINE
27#endif
28
29#include "device_table.h"
30
31#ifdef HAVE_TIME_H
32#include <time.h>
33#endif
34
35#ifdef HAVE_STRING_H
36#include <string.h>
37#else
38#ifdef HAVE_STRINGS_H
39#include <strings.h>
40#endif
41#endif
42
43/* DEVICE
44
45
46   nvram - non-volatile memory with clock
47
48
49   DESCRIPTION
50
51
52   This device implements a small byte addressable non-volatile
53   memory.  The top 8 bytes of this memory include a real-time clock.
54
55
56   PROPERTIES
57
58
59   reg = <address> <size> (required)
60
61   Specify the address/size of this device within its parents address
62   space.
63
64
65   timezone = <integer> (optional)
66
67   Adjustment to the hosts current GMT (in seconds) that should be
68   applied when updating the NVRAM's clock.  If no timezone is
69   specified, zero (GMT or UCT) is assumed.
70
71
72   */
73
74typedef struct _hw_nvram_device {
75  unsigned8 *memory;
76  unsigned sizeof_memory;
77#ifdef HAVE_TIME_H
78  time_t host_time;
79#else
80  long host_time;
81#endif
82  unsigned timezone;
83  /* useful */
84  unsigned addr_year;
85  unsigned addr_month;
86  unsigned addr_date;
87  unsigned addr_day;
88  unsigned addr_hour;
89  unsigned addr_minutes;
90  unsigned addr_seconds;
91  unsigned addr_control;
92} hw_nvram_device;
93
94static void *
95hw_nvram_create(const char *name,
96		const device_unit *unit_address,
97		const char *args)
98{
99  hw_nvram_device *nvram = ZALLOC(hw_nvram_device);
100  return nvram;
101}
102
103typedef struct _hw_nvram_reg_spec {
104  unsigned32 base;
105  unsigned32 size;
106} hw_nvram_reg_spec;
107
108static void
109hw_nvram_init_address(device *me)
110{
111  hw_nvram_device *nvram = (hw_nvram_device*)device_data(me);
112
113  /* use the generic init code to attach this device to its parent bus */
114  generic_device_init_address(me);
115
116  /* find the first non zero reg property and use that as the device
117     size */
118  if (nvram->sizeof_memory == 0) {
119    reg_property_spec reg;
120    int reg_nr;
121    for (reg_nr = 0;
122	 device_find_reg_array_property(me, "reg", reg_nr, &reg);
123	 reg_nr++) {
124      unsigned attach_size;
125      if (device_size_to_attach_size(device_parent(me),
126				     &reg.size, &attach_size,
127				     me)) {
128	nvram->sizeof_memory = attach_size;
129	break;
130      }
131    }
132    if (nvram->sizeof_memory == 0)
133      device_error(me, "reg property must contain a non-zero phys-addr:size tupple");
134    if (nvram->sizeof_memory < 8)
135      device_error(me, "NVRAM must be at least 8 bytes in size");
136  }
137
138  /* initialize the hw_nvram */
139  if (nvram->memory == NULL) {
140    nvram->memory = zalloc(nvram->sizeof_memory);
141  }
142  else
143    memset(nvram->memory, 0, nvram->sizeof_memory);
144
145  if (device_find_property(me, "timezone") == NULL)
146    nvram->timezone = 0;
147  else
148    nvram->timezone = device_find_integer_property(me, "timezone");
149
150  nvram->addr_year = nvram->sizeof_memory - 1;
151  nvram->addr_month = nvram->sizeof_memory - 2;
152  nvram->addr_date = nvram->sizeof_memory - 3;
153  nvram->addr_day = nvram->sizeof_memory - 4;
154  nvram->addr_hour = nvram->sizeof_memory - 5;
155  nvram->addr_minutes = nvram->sizeof_memory - 6;
156  nvram->addr_seconds = nvram->sizeof_memory - 7;
157  nvram->addr_control = nvram->sizeof_memory - 8;
158
159}
160
161static int
162hw_nvram_bcd(int val)
163{
164  val = val % 100;
165  if (val < 0)
166    val += 100;
167  return ((val / 10) << 4) + (val % 10);
168}
169
170
171/* If reached an update interval and allowed, update the clock within
172   the hw_nvram.  While this function could be implemented using events
173   it isn't on the assumption that the HW_NVRAM will hardly ever be
174   referenced and hence there is little need in keeping the clock
175   continually up-to-date */
176
177static void
178hw_nvram_update_clock(hw_nvram_device *nvram,
179		      cpu *processor)
180{
181#ifdef HAVE_TIME_H
182  if (!(nvram->memory[nvram->addr_control] & 0xc0)) {
183    time_t host_time = time(NULL);
184    if (nvram->host_time != host_time) {
185      time_t nvtime = host_time + nvram->timezone;
186      struct tm *clock = gmtime(&nvtime);
187      nvram->host_time = host_time;
188      nvram->memory[nvram->addr_year] = hw_nvram_bcd(clock->tm_year);
189      nvram->memory[nvram->addr_month] = hw_nvram_bcd(clock->tm_mon + 1);
190      nvram->memory[nvram->addr_date] = hw_nvram_bcd(clock->tm_mday);
191      nvram->memory[nvram->addr_day] = hw_nvram_bcd(clock->tm_wday + 1);
192      nvram->memory[nvram->addr_hour] = hw_nvram_bcd(clock->tm_hour);
193      nvram->memory[nvram->addr_minutes] = hw_nvram_bcd(clock->tm_min);
194      nvram->memory[nvram->addr_seconds] = hw_nvram_bcd(clock->tm_sec);
195    }
196  }
197#else
198  error("fixme - where do I find out GMT\n");
199#endif
200}
201
202static void
203hw_nvram_set_clock(hw_nvram_device *nvram, cpu *processor)
204{
205  error ("fixme - how do I set the localtime\n");
206}
207
208static unsigned
209hw_nvram_io_read_buffer(device *me,
210			void *dest,
211			int space,
212			unsigned_word addr,
213			unsigned nr_bytes,
214			cpu *processor,
215			unsigned_word cia)
216{
217  int i;
218  hw_nvram_device *nvram = (hw_nvram_device*)device_data(me);
219  for (i = 0; i < nr_bytes; i++) {
220    unsigned address = (addr + i) % nvram->sizeof_memory;
221    unsigned8 data = nvram->memory[address];
222    hw_nvram_update_clock(nvram, processor);
223    ((unsigned8*)dest)[i] = data;
224  }
225  return nr_bytes;
226}
227
228static unsigned
229hw_nvram_io_write_buffer(device *me,
230			 const void *source,
231			 int space,
232			 unsigned_word addr,
233			 unsigned nr_bytes,
234			 cpu *processor,
235			 unsigned_word cia)
236{
237  int i;
238  hw_nvram_device *nvram = (hw_nvram_device*)device_data(me);
239  for (i = 0; i < nr_bytes; i++) {
240    unsigned address = (addr + i) % nvram->sizeof_memory;
241    unsigned8 data = ((unsigned8*)source)[i];
242    if (address == nvram->addr_control
243	&& (data & 0x80) == 0
244	&& (nvram->memory[address] & 0x80) == 0x80)
245      hw_nvram_set_clock(nvram, processor);
246    else
247      hw_nvram_update_clock(nvram, processor);
248    nvram->memory[address] = data;
249  }
250  return nr_bytes;
251}
252
253static device_callbacks const hw_nvram_callbacks = {
254  { hw_nvram_init_address, },
255  { NULL, }, /* address */
256  { hw_nvram_io_read_buffer, hw_nvram_io_write_buffer }, /* IO */
257};
258
259const device_descriptor hw_nvram_device_descriptor[] = {
260  { "nvram", hw_nvram_create, &hw_nvram_callbacks },
261  { NULL },
262};
263
264#endif /* _HW_NVRAM_C_ */
265