| /* $Id: am9513.c,v 1.17 2010/06/05 14:36:59 fredette Exp $ */ |
| |
| /* ic/am9513.c - implementation of Am9513 emulation: */ |
| |
| /* |
| * Copyright (c) 2003 Matt Fredette |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by Matt Fredette. |
| * 4. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
| * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <tme/common.h> |
| _TME_RCSID("$Id: am9513.c,v 1.17 2010/06/05 14:36:59 fredette Exp $"); |
| |
| /* includes: */ |
| #include <tme/generic/bus-device.h> |
| #include <tme/ic/am9513.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| |
| /* macros: */ |
| #define TME_AM9513_CMD_SET_BUS_16BIT (0xffef) |
| #define TME_AM9513_CMD_RESET (0xffff) |
| #define TME_AM9513_CMD_LOAD_COUNTERS (0xff40) |
| #define TME_AM9513_CMD_CLEAR_TOGGLE_OUT (0xffe0) |
| #define TME_AM9513_CMD_LOAD_ARM_COUNTERS (0xff60) |
| #define TME_AM9513_CMD_DISARM_COUNTERS (0xffc0) |
| #define TME_AM9513_CMD_ARM_COUNTERS (0xff20) |
| #define _TME_AM9513_CMD_TIMERS_MASK (0x001f) |
| #define _TME_AM9513_CMD_TIMER_MASK (0x0007) |
| |
| /* bits in the Master Mode Register: */ |
| #define TME_AM9513_MMR_BUS_16BIT TME_BIT(13) |
| #define TME_AM9513_MMR_NO_INCREMENT TME_BIT(14) |
| |
| /* bits in the Status Register: */ |
| #define TME_AM9513_STATUS_BYTE_POINTER TME_BIT(0) |
| |
| /* the Counter Mode register: */ |
| #define TME_AM9513_CM_SOURCE_MASK (0x0f00) |
| #define TME_AM9513_CM_SOURCE_F1 (0x0b00) |
| #define TME_AM9513_CM_SOURCE_F2 (0x0c00) |
| #define TME_AM9513_CM_REPEAT_ENA (0x0020) |
| #define TME_AM9513_CM_OUTPUT_MASK (0x0007) |
| #define TME_AM9513_CM_OUTPUT_INACTIVE (0x0000) |
| #define TME_AM9513_CM_OUTPUT_TC_TOGGLED (0x0002) |
| |
| /* counter flags: */ |
| #define TME_AM9513_COUNTER_FLAG_ARMED TME_BIT(0) |
| |
| /* define this to track interrupt rates, reporting once every N |
| seconds: */ |
| #if 1 |
| #define TME_AM9513_TRACK_INT_RATE (10) |
| #endif |
| |
| #define TME_AM9513_LOG_HANDLE(am) (&(am)->tme_am9513_element->tme_element_log_handle) |
| |
| /* structures: */ |
| struct tme_am9513 { |
| |
| /* our simple bus device header: */ |
| struct tme_bus_device tme_am9513_device; |
| #define tme_am9513_element tme_am9513_device.tme_bus_device_element |
| |
| /* our socket: */ |
| struct tme_am9513_socket tme_am9513_socket; |
| #define tme_am9513_address_cmd tme_am9513_socket.tme_am9513_socket_address_cmd |
| #define tme_am9513_address_data tme_am9513_socket.tme_am9513_socket_address_data |
| #define tme_am9513_port_least_lane tme_am9513_socket.tme_am9513_socket_port_least_lane |
| #define tme_am9513_basic_clock tme_am9513_socket.tme_am9513_socket_basic_clock |
| tme_uint32_t tme_am9513_basic_clock_msec; |
| |
| /* our mutex: */ |
| tme_mutex_t tme_am9513_mutex; |
| |
| /* this is nonzero iff callouts are running: */ |
| int tme_am9513_callouts_running; |
| |
| /* the control registers: */ |
| tme_uint16_t tme_am9513_control_regs[4]; |
| #define tme_am9513_alarm1 tme_am9513_control_regs[0] |
| #define tme_am9513_alarm2 tme_am9513_control_regs[1] |
| #define tme_am9513_mmr tme_am9513_control_regs[2] |
| #define tme_am9513_status tme_am9513_control_regs[3] |
| |
| /* our counters: */ |
| struct tme_am9513_counter { |
| tme_uint16_t tme_am9513_counter_regs[4]; |
| #define tme_am9513_counter_mode tme_am9513_counter_regs[0] |
| #define tme_am9513_counter_load tme_am9513_counter_regs[1] |
| #define tme_am9513_counter_hold tme_am9513_counter_regs[2] |
| #define tme_am9513_counter_cntr tme_am9513_counter_regs[3] |
| unsigned int tme_am9513_counter_flags; |
| #ifdef TME_AM9513_TRACK_INT_RATE |
| unsigned long tme_am9513_counter_int_sample; |
| struct timeval tme_am9513_counter_int_sample_time; |
| #endif /* TME_AM9513_TRACK_INT_RATE */ |
| } tme_am9513_counters[5]; |
| |
| /* the data pointer register: */ |
| tme_uint8_t tme_am9513_data_pointer; |
| |
| /* the current output pins: */ |
| tme_uint8_t tme_am9513_output_pins; |
| |
| /* the last output pins we called out: */ |
| tme_uint8_t tme_am9513_output_pins_last; |
| |
| /* the last time our connection thread ran: */ |
| struct timeval tme_am9513_conn_last; |
| }; |
| |
| /* the Am9513 doesn't have any concept of endianness because it has a |
| single "address bit" (the C/!D pin), so when we're running cycles |
| we always read and write an entire 16-bit port using a data cycle |
| buffer that points to a 16-bit variable, routing the least |
| significant lane to the least significant part of the variable, |
| etc.: */ |
| #ifdef WORDS_BIGENDIAN |
| #define REG_LO (1) |
| #define REG_HI (0) |
| #else |
| #define REG_LO (0) |
| #define REG_HI (1) |
| #endif |
| |
| /* the am9513 bus router: */ |
| static const tme_bus_lane_t tme_am9513_router[TME_BUS_ROUTER_SIZE(TME_BUS16_LOG2)] = { |
| |
| /* [gen] initiator port size: 8 bits |
| [gen] initiator port least lane: 0: */ |
| /* D7-D0 */ TME_BUS_LANE_ROUTE(REG_LO), |
| /* D15-D8 */ TME_BUS_LANE_UNDEF, |
| |
| /* [gen] initiator port size: 8 bits |
| [gen] initiator port least lane: 1: */ |
| /* D7-D0 */ TME_BUS_LANE_UNDEF, |
| /* D15-D8 */ TME_BUS_LANE_ROUTE(REG_HI), |
| |
| /* [gen] initiator port size: 16 bits |
| [gen] initiator port least lane: 0: */ |
| /* D7-D0 */ TME_BUS_LANE_ROUTE(REG_LO), |
| /* D15-D8 */ TME_BUS_LANE_ROUTE(REG_HI), |
| |
| /* [gen] initiator port size: 16 bits |
| [gen] initiator port least lane: 1 - invalid, array placeholder: */ |
| /* D7-D0 */ TME_BUS_LANE_ABORT, |
| /* D15-D8 */ TME_BUS_LANE_ABORT |
| }; |
| |
| /* this resets an am9513: */ |
| static void |
| _tme_am9513_reset(struct tme_am9513 *am9513) |
| { |
| unsigned int counter_i; |
| struct tme_am9513_counter *counter; |
| |
| /* disarm all counters: */ |
| /* XXX */ |
| |
| /* enter 0000 into the Master Mode register: */ |
| am9513->tme_am9513_mmr = 0; |
| |
| /* initialize all of the counters: */ |
| for (counter_i = 0; |
| counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters); |
| counter_i++) { |
| counter = &am9513->tme_am9513_counters[counter_i]; |
| |
| counter->tme_am9513_counter_mode = 0x0b00; |
| counter->tme_am9513_counter_load = 0x0000; |
| counter->tme_am9513_counter_hold = 0x0000; |
| counter->tme_am9513_counter_cntr = 0x0000; |
| counter->tme_am9513_counter_flags = 0; |
| } |
| } |
| |
| /* this loads counters: */ |
| static void |
| _tme_am9513_counters_load(struct tme_am9513 *am9513, tme_uint16_t counters_mask) |
| { |
| unsigned int counter_i; |
| struct tme_am9513_counter *counter; |
| |
| /* load all selected counters: */ |
| for (counter_i = 0; |
| counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters); |
| counter_i++) { |
| if (counters_mask & TME_BIT(counter_i)) { |
| counter = &am9513->tme_am9513_counters[counter_i]; |
| counter->tme_am9513_counter_cntr = counter->tme_am9513_counter_load; |
| } |
| } |
| } |
| |
| /* this arms counters: */ |
| static void |
| _tme_am9513_counters_arm(struct tme_am9513 *am9513, tme_uint16_t counters_mask) |
| { |
| unsigned int counter_i; |
| struct tme_am9513_counter *counter; |
| |
| /* load all selected counters: */ |
| for (counter_i = 0; |
| counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters); |
| counter_i++) { |
| if (counters_mask & TME_BIT(counter_i)) { |
| counter = &am9513->tme_am9513_counters[counter_i]; |
| counter->tme_am9513_counter_flags |= TME_AM9513_COUNTER_FLAG_ARMED; |
| } |
| } |
| } |
| |
| /* this disarms counters: */ |
| static void |
| _tme_am9513_counters_disarm(struct tme_am9513 *am9513, tme_uint16_t counters_mask) |
| { |
| unsigned int counter_i; |
| struct tme_am9513_counter *counter; |
| |
| /* load all selected counters: */ |
| for (counter_i = 0; |
| counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters); |
| counter_i++) { |
| if (counters_mask & TME_BIT(counter_i)) { |
| counter = &am9513->tme_am9513_counters[counter_i]; |
| counter->tme_am9513_counter_flags &= ~TME_AM9513_COUNTER_FLAG_ARMED; |
| } |
| } |
| } |
| |
| /* this makes am9513 callouts. it must be called with the mutex held: */ |
| static void |
| _tme_am9513_callout(struct tme_am9513 *am9513) |
| { |
| struct tme_bus_connection *conn_bus; |
| unsigned int counter_i; |
| int again; |
| int pin_high; |
| unsigned int signal; |
| int rc; |
| |
| /* if this function is already running in another thread, return |
| now. the other thread will do our work: */ |
| if (am9513->tme_am9513_callouts_running) { |
| return; |
| } |
| |
| /* callouts are now running: */ |
| am9513->tme_am9513_callouts_running = TRUE; |
| |
| /* get our bus connection: */ |
| conn_bus = tme_memory_atomic_pointer_read(struct tme_bus_connection *, |
| am9513->tme_am9513_device.tme_bus_device_connection, |
| &am9513->tme_am9513_device.tme_bus_device_connection_rwlock); |
| |
| /* loop forever: */ |
| for (again = TRUE; again;) { |
| again = FALSE; |
| |
| /* check all of the counter output pins for changes: */ |
| for (counter_i = 0; |
| counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters); |
| counter_i++) { |
| |
| /* skip this output pin if it hasn't changed: */ |
| if (((am9513->tme_am9513_output_pins |
| ^ am9513->tme_am9513_output_pins_last) |
| & TME_BIT(counter_i)) == 0) { |
| continue; |
| } |
| |
| /* see if this pin is edging high or low: */ |
| pin_high = am9513->tme_am9513_output_pins & TME_BIT(counter_i); |
| |
| /* get any bus signal this pin maps to: */ |
| signal = am9513->tme_am9513_socket.tme_am9513_socket_counter_signals[counter_i]; |
| |
| /* if this signal is ignored: */ |
| if (TME_BUS_SIGNAL_WHICH(signal) == TME_BUS_SIGNAL_IGNORE) { |
| rc = TME_OK; |
| } |
| |
| /* call out this signal edge: */ |
| else { |
| |
| /* unlock our mutex: */ |
| tme_mutex_unlock(&am9513->tme_am9513_mutex); |
| |
| rc = (*conn_bus->tme_bus_signal) |
| (conn_bus, |
| signal |
| ^ (pin_high |
| ? TME_BUS_SIGNAL_LEVEL_HIGH |
| : TME_BUS_SIGNAL_LEVEL_LOW)); |
| |
| /* lock our mutex: */ |
| tme_mutex_lock(&am9513->tme_am9513_mutex); |
| } |
| |
| /* if this call out succeeded, update the pin: */ |
| if (rc == TME_OK) { |
| am9513->tme_am9513_output_pins_last = |
| ((am9513->tme_am9513_output_pins_last |
| & ~TME_BIT(counter_i)) |
| | pin_high); |
| again = TRUE; |
| } |
| } |
| } |
| |
| /* there are no more callouts to make: */ |
| am9513->tme_am9513_callouts_running = FALSE; |
| } |
| |
| /* the am9513 timer thread: */ |
| static void |
| _tme_am9513_th_timer(struct tme_am9513 *am9513) |
| { |
| struct timeval then, elapsed; |
| tme_uint16_t counter_mode; |
| tme_uint32_t basic_elapsed; |
| tme_uint32_t basic_sleep; |
| tme_uint32_t divisor; |
| unsigned int counter_i; |
| struct tme_am9513_counter *counter; |
| tme_uint32_t counter_elapsed; |
| |
| /* loop forever: */ |
| for (;;) { |
| |
| /* figure out how much time has elapsed since our last run: */ |
| gettimeofday(&elapsed, NULL); |
| then = am9513->tme_am9513_conn_last; |
| am9513->tme_am9513_conn_last = elapsed; |
| if (elapsed.tv_usec < then.tv_usec) { |
| elapsed.tv_sec--; |
| elapsed.tv_usec += 1000000; |
| } |
| elapsed.tv_sec -= then.tv_sec; |
| elapsed.tv_usec -= then.tv_usec; |
| |
| /* calculate the number of basic ticks that have elapsed: */ |
| basic_elapsed = am9513->tme_am9513_basic_clock; |
| basic_elapsed *= elapsed.tv_sec; |
| basic_elapsed += (am9513->tme_am9513_basic_clock_msec * elapsed.tv_usec) / 1000; |
| |
| /* assume that we will sleep for one second: */ |
| basic_sleep = am9513->tme_am9513_basic_clock; |
| |
| /* lock our mutex: */ |
| tme_mutex_lock(&am9513->tme_am9513_mutex); |
| |
| /* check all of the counters: */ |
| for (counter_i = 0; |
| counter_i < TME_ARRAY_ELS(am9513->tme_am9513_counters); |
| counter_i++) { |
| counter = &am9513->tme_am9513_counters[counter_i]; |
| counter_mode = counter->tme_am9513_counter_mode; |
| |
| /* dispatch on the counter source: */ |
| switch (counter_mode & TME_AM9513_CM_SOURCE_MASK) { |
| |
| case TME_AM9513_CM_SOURCE_F2: |
| case TME_AM9513_CM_SOURCE_F1: |
| |
| /* if this counter is armed: */ |
| if (counter->tme_am9513_counter_flags & TME_AM9513_COUNTER_FLAG_ARMED) { |
| |
| /* calculate the divisor: */ |
| divisor = (1 << (((counter->tme_am9513_counter_mode & TME_AM9513_CM_SOURCE_MASK) |
| - TME_AM9513_CM_SOURCE_F1) >> 6)); |
| |
| /* calculate the number of ticks on this counter that have happened: */ |
| counter_elapsed = basic_elapsed / divisor; |
| |
| /* while this counter (repeatedly) reaches zero: */ |
| for (; counter->tme_am9513_counter_cntr <= counter_elapsed; ) { |
| counter_elapsed -= counter->tme_am9513_counter_cntr; |
| |
| /* dispatch on this counter's output control: */ |
| switch (counter_mode & TME_AM9513_CM_OUTPUT_MASK) { |
| case TME_AM9513_CM_OUTPUT_INACTIVE: |
| break; |
| case TME_AM9513_CM_OUTPUT_TC_TOGGLED: |
| |
| /* as a slightly sun2-specific hack, if this timer |
| output becomes an interrupt signal, we only set it, |
| and never clear it. if we find it set, that means |
| clock interrupt latency is high: */ |
| if (TME_BUS_SIGNAL_IS_INT(am9513->tme_am9513_socket.tme_am9513_socket_counter_signals[counter_i])) { |
| #ifdef TME_AM9513_TRACK_INT_RATE |
| if (!(am9513->tme_am9513_output_pins & TME_BIT(counter_i))) { |
| counter->tme_am9513_counter_int_sample++; |
| am9513->tme_am9513_output_pins |= TME_BIT(counter_i); |
| } |
| #else /* !TME_AM9513_TRACK_INT_RATE */ |
| am9513->tme_am9513_output_pins |= TME_BIT(counter_i); |
| #endif /* !TME_AM9513_TRACK_INT_RATE */ |
| } |
| |
| /* otherwise, always toggle it like we're supposed to: */ |
| else { |
| am9513->tme_am9513_output_pins ^= TME_BIT(counter_i); |
| } |
| break; |
| default: |
| abort(); |
| } |
| |
| /* if this counter reloads, reload it, otherwise disarm it: */ |
| /* XXX is disarming it the right thing to do? */ |
| if (counter_mode & TME_AM9513_CM_REPEAT_ENA) { |
| counter->tme_am9513_counter_cntr = counter->tme_am9513_counter_load; |
| } |
| else { |
| counter_elapsed = counter->tme_am9513_counter_cntr; |
| counter->tme_am9513_counter_flags &= ~TME_AM9513_COUNTER_FLAG_ARMED; |
| break; |
| } |
| } |
| counter->tme_am9513_counter_cntr -= counter_elapsed; |
| |
| /* calculate the number of basic ticks until this counter expires: */ |
| if (counter->tme_am9513_counter_cntr > 0) { |
| basic_sleep = TME_MIN(basic_sleep, |
| counter->tme_am9513_counter_cntr * divisor); |
| } |
| } |
| |
| break; |
| |
| default: |
| abort(); |
| } |
| |
| /* no other bits can be set in the mode: */ |
| if (counter_mode & ~(TME_AM9513_CM_SOURCE_MASK |
| | TME_AM9513_CM_REPEAT_ENA |
| | TME_AM9513_CM_OUTPUT_MASK)) { |
| abort(); |
| } |
| |
| #ifdef TME_AM9513_TRACK_INT_RATE |
| |
| /* update the sample time: */ |
| for (counter->tme_am9513_counter_int_sample_time.tv_usec += elapsed.tv_usec; |
| counter->tme_am9513_counter_int_sample_time.tv_usec >= 1000000; |
| counter->tme_am9513_counter_int_sample_time.tv_usec -= 1000000) { |
| counter->tme_am9513_counter_int_sample_time.tv_sec++; |
| } |
| counter->tme_am9513_counter_int_sample_time.tv_sec += elapsed.tv_sec; |
| |
| /* if the sample time has finished, report on the interrupt |
| rate: */ |
| if (counter->tme_am9513_counter_int_sample_time.tv_sec |
| >= TME_AM9513_TRACK_INT_RATE) { |
| if (counter->tme_am9513_counter_int_sample > 0) { |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), |
| 0, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "timer %d interrupt rate: %ld/sec", |
| counter_i, |
| (counter->tme_am9513_counter_int_sample |
| / (unsigned long) counter->tme_am9513_counter_int_sample_time.tv_sec))); |
| } |
| |
| /* reset the sample: */ |
| counter->tme_am9513_counter_int_sample_time.tv_sec = 0; |
| counter->tme_am9513_counter_int_sample_time.tv_usec = 0; |
| counter->tme_am9513_counter_int_sample = 0; |
| } |
| #endif /* TME_AM9513_TRACK_INT_RATE */ |
| } |
| |
| /* if we need to, call out for the output pins: */ |
| if (am9513->tme_am9513_output_pins |
| != am9513->tme_am9513_output_pins_last) { |
| _tme_am9513_callout(am9513); |
| } |
| |
| /* unlock our mutex: */ |
| tme_mutex_unlock(&am9513->tme_am9513_mutex); |
| |
| /* sleep: */ |
| tme_thread_sleep_yield(0, (basic_sleep * 1000) / am9513->tme_am9513_basic_clock_msec); |
| } |
| /* NOTREACHED */ |
| } |
| |
| /* the am9513 bus cycle handler: */ |
| static int |
| _tme_am9513_bus_cycle(void *_am9513, struct tme_bus_cycle *cycle_init) |
| { |
| struct tme_am9513 *am9513; |
| tme_bus_addr32_t am9513_address_last; |
| tme_uint8_t data_pointer, group_pointer, element_pointer, data_pointer_next; |
| tme_uint16_t byte_pointer; |
| int is_cmd; |
| tme_uint16_t *value, buffer, cmd; |
| struct tme_am9513_counter *counter; |
| struct tme_bus_cycle cycle_resp; |
| int need_callout; |
| |
| /* recover our data structure: */ |
| am9513 = (struct tme_am9513 *) _am9513; |
| |
| /* the requested cycle must be within range: */ |
| am9513_address_last = am9513->tme_am9513_device.tme_bus_device_address_last; |
| assert(cycle_init->tme_bus_cycle_address <= am9513_address_last); |
| assert(cycle_init->tme_bus_cycle_size <= (am9513_address_last - cycle_init->tme_bus_cycle_address) + 1); |
| |
| /* see if this is a command or data access: */ |
| is_cmd = ((am9513->tme_am9513_address_cmd |
| > am9513->tme_am9513_address_data) |
| == (cycle_init->tme_bus_cycle_address |
| >= TME_MAX(am9513->tme_am9513_address_cmd, |
| am9513->tme_am9513_address_data))); |
| |
| /* assume that we won't need to call out: */ |
| need_callout = FALSE; |
| |
| /* lock the mutex: */ |
| tme_mutex_lock(&am9513->tme_am9513_mutex); |
| |
| /* assume there is no counter involved in this cycle, get out |
| the data pointer, and assume that the data pointer will be |
| unchanged: */ |
| counter = NULL; |
| data_pointer = am9513->tme_am9513_data_pointer; |
| data_pointer_next = data_pointer; |
| byte_pointer = (am9513->tme_am9513_status & TME_AM9513_STATUS_BYTE_POINTER); |
| |
| /* initialize value to silence -Wuninitialized: */ |
| value = NULL; |
| |
| /* reads of the command register get the status, and writes to the |
| command register are just processed: */ |
| if (is_cmd) { |
| value = (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ |
| ? &am9513->tme_am9513_status |
| : NULL); |
| byte_pointer = TME_AM9513_STATUS_BYTE_POINTER; |
| } |
| |
| /* transfers to or from the data register involve whatever the Data |
| Pointer Register is pointing to: */ |
| else { |
| |
| /* take out the Group Pointer and Element Pointer fields: */ |
| group_pointer = TME_FIELD_EXTRACTU(data_pointer, 0, 3); |
| element_pointer = TME_FIELD_EXTRACTU(data_pointer, 3, 2); |
| |
| /* dispatch on the Group Pointer: */ |
| switch (group_pointer) { |
| |
| /* Groups 000 and 110 are illegal: */ |
| case 0: |
| case 6: |
| value = &buffer; |
| break; |
| |
| /* Groups 001 through 101 are Counters: */ |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| case 5: |
| counter = &am9513->tme_am9513_counters[group_pointer - 1]; |
| |
| /* dispatch on the Element Pointer: */ |
| switch (element_pointer) { |
| |
| /* 00 = Mode Register, Element Cycle Increment: */ |
| /* 01 = Load Register, Element Cycle Increment: */ |
| /* 10 = Hold Register, Element Cycle Increment: */ |
| case 0: |
| case 1: |
| case 2: |
| value = &counter->tme_am9513_counter_regs[element_pointer]; |
| element_pointer++; |
| if (element_pointer == TME_ARRAY_ELS(counter->tme_am9513_counter_regs)) { |
| element_pointer = 0; |
| group_pointer++; |
| if (group_pointer - 1 == TME_ARRAY_ELS(am9513->tme_am9513_counters)) { |
| group_pointer = 1; |
| } |
| } |
| break; |
| |
| /* 11 = Hold Register, Hold Cycle Increment: */ |
| case 3: |
| value = &counter->tme_am9513_counter_hold; |
| group_pointer++; |
| if (group_pointer - 1 == TME_ARRAY_ELS(am9513->tme_am9513_counters)) { |
| group_pointer = 1; |
| } |
| break; |
| } |
| |
| /* if this is a read, log it. remember that the element pointer |
| has been incremented: */ |
| if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ) { |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), |
| 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "read Timer %d %s Register (data pointer 0x%02x) = 0x%04x", |
| group_pointer, |
| (element_pointer == 1 |
| ? "Mode" |
| : element_pointer == 2 |
| ? "Load" |
| : "Hold"), |
| data_pointer, |
| *value)); |
| } |
| |
| break; |
| |
| /* Group 111 is the Control Group: */ |
| case 7: |
| |
| /* dispatch on the Element Pointer: */ |
| switch (element_pointer) { |
| |
| /* 00 = Alarm Register 1, Control Cycle Increment: */ |
| /* 01 = Alarm Register 2, Control Cycle Increment: */ |
| /* 10 = Master Mode Register, Control Cycle Increment: */ |
| case 0: |
| case 1: |
| case 2: |
| value = &am9513->tme_am9513_control_regs[element_pointer]; |
| element_pointer++; |
| if (element_pointer == TME_ARRAY_ELS(am9513->tme_am9513_control_regs)) { |
| element_pointer = 0; |
| } |
| break; |
| |
| /* 11 = Status Register, No Increment: */ |
| case 3: |
| value = &am9513->tme_am9513_status; |
| break; |
| } |
| |
| /* if this is a read, log it. remember that the element pointer |
| has been incremented: */ |
| if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ) { |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), |
| 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "read %s (data pointer 0x%02x) = 0x%04x", |
| (element_pointer == 1 |
| ? "Alarm Register 1" |
| : element_pointer == 2 |
| ? "Alarm Register 2" |
| : element_pointer == 0 |
| ? "Master Mode Register" |
| : "Status Register"), |
| data_pointer, |
| *value)); |
| } |
| |
| break; |
| } |
| |
| /* set what the next data pointer might be: */ |
| data_pointer_next = (element_pointer << 3) | group_pointer; |
| } |
| |
| /* get the value to route on the bus. if we're in 8-bit bus mode, |
| route in D7-D0 either the low byte of the value or the high byte, |
| depending on the Byte Pointer bit, with D15-D8 as all-bits-one: */ |
| buffer = (value == NULL ? 0xffff : *value); |
| if ((am9513->tme_am9513_mmr & TME_AM9513_MMR_BUS_16BIT) == 0) { |
| if (byte_pointer == 0) { |
| buffer >>= 8; |
| } |
| buffer |= 0xff00; |
| } |
| |
| /* run the bus cycle: */ |
| cycle_resp.tme_bus_cycle_buffer = (tme_uint8_t *) &buffer; |
| cycle_resp.tme_bus_cycle_lane_routing = tme_am9513_router; |
| cycle_resp.tme_bus_cycle_address = 0; |
| cycle_resp.tme_bus_cycle_buffer_increment = 1; |
| cycle_resp.tme_bus_cycle_type = |
| (cycle_init->tme_bus_cycle_type |
| ^ (TME_BUS_CYCLE_READ |
| | TME_BUS_CYCLE_WRITE)); |
| cycle_resp.tme_bus_cycle_size = sizeof(tme_uint16_t); |
| cycle_resp.tme_bus_cycle_port = |
| TME_BUS_CYCLE_PORT(am9513->tme_am9513_port_least_lane, |
| TME_BUS16_LOG2); |
| tme_bus_cycle_xfer(cycle_init, &cycle_resp); |
| |
| /* if this is a write: */ |
| if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_WRITE) { |
| |
| /* if this is a write to the command register: */ |
| if (is_cmd) { |
| |
| /* dispatch on the command: */ |
| cmd = buffer; |
| |
| /* set 16-bit mode: */ |
| if (cmd == TME_AM9513_CMD_SET_BUS_16BIT) { |
| am9513->tme_am9513_mmr |= TME_AM9513_MMR_BUS_16BIT; |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), "set 16-bit bus mode")); |
| } |
| |
| /* load data pointer: */ |
| else if ((cmd & 0xe0) == 0x00) { |
| am9513->tme_am9513_data_pointer = (cmd & 0x1f); |
| am9513->tme_am9513_status |= TME_AM9513_STATUS_BYTE_POINTER; |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "set data pointer to %02x", |
| am9513->tme_am9513_data_pointer)); |
| } |
| |
| /* master reset: */ |
| else if (cmd == TME_AM9513_CMD_RESET) { |
| _tme_am9513_reset(am9513); |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), "master reset")); |
| } |
| |
| /* load counters: */ |
| else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK) |
| == TME_AM9513_CMD_LOAD_COUNTERS) { |
| _tme_am9513_counters_load(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK); |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "load counters, timer mask 0x%02x", |
| (cmd & _TME_AM9513_CMD_TIMERS_MASK))); |
| } |
| |
| /* clear toggle out: */ |
| else if ((cmd & ~_TME_AM9513_CMD_TIMER_MASK) |
| == TME_AM9513_CMD_CLEAR_TOGGLE_OUT) { |
| |
| /* clear the output pin for the given timer: */ |
| group_pointer = cmd & _TME_AM9513_CMD_TIMER_MASK; |
| am9513->tme_am9513_output_pins &= ~TME_BIT(group_pointer - 1); |
| need_callout = TRUE; |
| |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "Timer %d clear toggle out", |
| (cmd & _TME_AM9513_CMD_TIMER_MASK))); |
| } |
| |
| /* load and arm counters: */ |
| else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK) |
| == TME_AM9513_CMD_LOAD_ARM_COUNTERS) { |
| _tme_am9513_counters_load(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK); |
| _tme_am9513_counters_arm(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK); |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "load and arm counters, timer mask 0x%02x", |
| (cmd & _TME_AM9513_CMD_TIMERS_MASK))); |
| } |
| |
| /* disarm counters: */ |
| else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK) |
| == TME_AM9513_CMD_DISARM_COUNTERS) { |
| _tme_am9513_counters_disarm(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK); |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "disarm counters, timer mask 0x%02x", |
| (cmd & _TME_AM9513_CMD_TIMERS_MASK))); |
| } |
| |
| /* arm counters: */ |
| else if ((cmd & ~_TME_AM9513_CMD_TIMERS_MASK) |
| == TME_AM9513_CMD_ARM_COUNTERS) { |
| _tme_am9513_counters_arm(am9513, cmd & _TME_AM9513_CMD_TIMERS_MASK); |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "arm counters, timer mask 0x%02x", |
| (cmd & _TME_AM9513_CMD_TIMERS_MASK))); |
| } |
| |
| else { |
| abort(); |
| } |
| } |
| |
| /* otherwise this is a write to a data register: */ |
| else { |
| |
| /* take out the Group Pointer and Element Pointer fields: */ |
| group_pointer = TME_FIELD_EXTRACTU(data_pointer, 0, 3); |
| element_pointer = TME_FIELD_EXTRACTU(data_pointer, 3, 2); |
| |
| /* update the register: */ |
| if ((am9513->tme_am9513_mmr & TME_AM9513_MMR_BUS_16BIT) == 0) { |
| if (byte_pointer) { |
| *value = (*value & 0xff00) | (buffer & 0xff); |
| } |
| else { |
| *value = (*value & 0x00ff) | ((buffer & 0xff) << 8); |
| } |
| } |
| else { |
| *value = buffer; |
| } |
| |
| /* if this changed a counter register: */ |
| if (counter != NULL) { |
| |
| /* log the write: */ |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "write Timer %d %s Register (data pointer 0x%02x) = 0x%04x", |
| group_pointer, |
| (element_pointer == 0 |
| ? "Mode" |
| : element_pointer == 1 |
| ? "Load" |
| : "Hold"), |
| data_pointer, |
| *value)); |
| |
| /* dispatch on the Element Pointer: */ |
| switch (element_pointer) { |
| |
| /* 00 = Mode Register: */ |
| case 0: |
| /* XXX TBD */ |
| break; |
| |
| /* 01 = Load Register: */ |
| case 1: |
| /* XXX TBD */ |
| break; |
| |
| /* 10 = Hold Register: */ |
| /* 11 = Hold Register: */ |
| case 2: |
| case 3: |
| /* XXX TBD */ |
| break; |
| } |
| } |
| |
| /* if this changed a control register: */ |
| else { |
| |
| /* log the write: */ |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "write %s (data pointer 0x%02x) = 0x%04x", |
| (element_pointer == 0 |
| ? "Alarm Register 1" |
| : element_pointer == 1 |
| ? "Alarm Register 2" |
| : element_pointer == 2 |
| ? "Master Mode Register" |
| : "Status Register"), |
| data_pointer, |
| *value)); |
| |
| /* dispatch on the Element Pointer: */ |
| switch (element_pointer) { |
| |
| /* 00 = Alarm Register 1: */ |
| case 0: |
| /* XXX TBD */ |
| break; |
| |
| /* 01 = Alarm Register 2: */ |
| case 1: |
| /* XXX TBD */ |
| break; |
| |
| /* 10 = Master Mode Register: */ |
| case 2: |
| /* XXX TBD */ |
| break; |
| |
| /* 11 = Status Register: */ |
| case 3: |
| /* XXX TBD */ |
| break; |
| } |
| |
| } |
| } |
| } |
| |
| /* update the byte pointer and data pointer register as needed: */ |
| if (!is_cmd) { |
| if ((am9513->tme_am9513_mmr & TME_AM9513_MMR_BUS_16BIT) == 0) { |
| byte_pointer ^= TME_AM9513_STATUS_BYTE_POINTER; |
| } |
| am9513->tme_am9513_status = ((am9513->tme_am9513_status |
| & ~TME_AM9513_STATUS_BYTE_POINTER) |
| | byte_pointer); |
| if (byte_pointer != 0 |
| && !(am9513->tme_am9513_mmr & TME_AM9513_MMR_NO_INCREMENT)) { |
| am9513->tme_am9513_data_pointer = data_pointer_next; |
| } |
| tme_log(TME_AM9513_LOG_HANDLE(am9513), 100000, TME_OK, |
| (TME_AM9513_LOG_HANDLE(am9513), |
| "data pointer now 0x%02x (byte pointer %d)", |
| am9513->tme_am9513_data_pointer, |
| byte_pointer / TME_AM9513_STATUS_BYTE_POINTER)); |
| } |
| |
| /* if we need callouts, make them: */ |
| if (need_callout) { |
| _tme_am9513_callout(am9513); |
| } |
| |
| /* unlock the mutex: */ |
| tme_mutex_unlock(&am9513->tme_am9513_mutex); |
| |
| /* no faults: */ |
| return (TME_OK); |
| } |
| |
| /* the am9513 TLB filler: */ |
| static int |
| _tme_am9513_tlb_fill(void *_am9513, struct tme_bus_tlb *tlb, |
| tme_bus_addr_t address, unsigned int cycles) |
| { |
| struct tme_am9513 *am9513; |
| tme_bus_addr32_t am9513_address_last; |
| |
| /* recover our data structure: */ |
| am9513 = (struct tme_am9513 *) _am9513; |
| |
| /* the address must be within range: */ |
| am9513_address_last = am9513->tme_am9513_device.tme_bus_device_address_last; |
| assert(address <= am9513_address_last); |
| |
| /* initialize the TLB entry: */ |
| tme_bus_tlb_initialize(tlb); |
| |
| /* this TLB entry can cover the whole device: */ |
| tlb->tme_bus_tlb_addr_first = 0; |
| tlb->tme_bus_tlb_addr_last = am9513_address_last; |
| |
| /* allow reading and writing: */ |
| tlb->tme_bus_tlb_cycles_ok = TME_BUS_CYCLE_READ | TME_BUS_CYCLE_WRITE; |
| |
| /* our bus cycle handler: */ |
| tlb->tme_bus_tlb_cycle_private = am9513; |
| tlb->tme_bus_tlb_cycle = _tme_am9513_bus_cycle; |
| |
| return (TME_OK); |
| } |
| |
| /* the new am9513 element function: */ |
| TME_ELEMENT_NEW_DECL(tme_ic_am9513) { |
| const struct tme_am9513_socket *socket; |
| struct tme_am9513 *am9513; |
| struct tme_am9513_socket socket_real; |
| tme_bus_addr_t address_mask; |
| |
| /* dispatch on our socket version: */ |
| socket = (const struct tme_am9513_socket *) extra; |
| if (socket == NULL) { |
| tme_output_append_error(_output, _("need an ic socket")); |
| return (ENXIO); |
| } |
| switch (socket->tme_am9513_socket_version) { |
| case TME_AM9513_SOCKET_0: |
| socket_real = *socket; |
| break; |
| default: |
| tme_output_append_error(_output, _("socket type")); |
| return (EOPNOTSUPP); |
| } |
| |
| /* we take no arguments: */ |
| if (args[1] != NULL) { |
| tme_output_append_error(_output, |
| "%s %s, %s %s", |
| args[1], |
| _("unexpected"), |
| _("usage:"), |
| args[0]); |
| return (EINVAL); |
| } |
| |
| /* start the am9513 structure: */ |
| am9513 = tme_new0(struct tme_am9513, 1); |
| am9513->tme_am9513_socket = socket_real; |
| am9513->tme_am9513_basic_clock_msec = am9513->tme_am9513_basic_clock / 1000; |
| am9513->tme_am9513_element = element; |
| _tme_am9513_reset(am9513); |
| |
| /* figure our address mask, up to the nearest power of two: */ |
| address_mask = TME_MAX(am9513->tme_am9513_address_cmd, |
| am9513->tme_am9513_address_data); |
| address_mask += sizeof(tme_uint16_t); |
| if (address_mask & (address_mask - 1)) { |
| for (; address_mask & (address_mask - 1); address_mask &= (address_mask - 1)); |
| address_mask <<= 1; |
| } |
| address_mask -= 1; |
| |
| /* initialize our simple bus device descriptor: */ |
| am9513->tme_am9513_device.tme_bus_device_tlb_fill = _tme_am9513_tlb_fill; |
| am9513->tme_am9513_device.tme_bus_device_address_last = address_mask; |
| |
| /* start the timer thread: */ |
| tme_mutex_init(&am9513->tme_am9513_mutex); |
| tme_thread_create((tme_thread_t) _tme_am9513_th_timer, am9513); |
| |
| /* fill the element: */ |
| element->tme_element_private = am9513; |
| element->tme_element_connections_new = tme_bus_device_connections_new; |
| |
| return (TME_OK); |
| } |