/* IBM_PROLOG_BEGIN_TAG */ /* This is an automatically generated prolog. */ /* */ /* $Source: src/ssx/occhw/occhw_ocb.c $ */ /* */ /* OpenPOWER OnChipController Project */ /* */ /* Contributors Listed Below - COPYRIGHT 2015,2016 */ /* [+] International Business Machines Corp. */ /* */ /* */ /* Licensed under the Apache License, Version 2.0 (the "License"); */ /* you may not use this file except in compliance with the License. */ /* You may obtain a copy of the License at */ /* */ /* http://www.apache.org/licenses/LICENSE-2.0 */ /* */ /* Unless required by applicable law or agreed to in writing, software */ /* distributed under the License is distributed on an "AS IS" BASIS, */ /* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or */ /* implied. See the License for the specific language governing */ /* permissions and limitations under the License. */ /* */ /* IBM_PROLOG_END_TAG */ /// \file occhw_ocb.c /// \brief OCB-related drivers for OCCHW #include "ssx.h" //use compile-time default in case the OCB timer is never used -- grm unsigned int g_ocb_timer_divider = OCB_TIMER_DIVIDER_DEFAULT; /// Reset an OCB timer /// /// \param timer A valid OCB timer index /// /// \param auto_reload A non-0 value indicates to run the timer in auto-reload /// mode. /// /// \param timeout_ns The timeout specified in nanoseconds. The actual timeout /// will be rounded down to the underlying timer tick, with a minimum 1 tick /// timeout. However if the \a timeout_ns argument is explicity 0, then the /// timer wil be initialized with a 0 timeout, effectively disabling the /// timer. /// /// Reseting an OCB timer means rewriting the timer control register with a /// potentially new auto-reload enable and new timeout value. This API also /// clears any pending timer interrupt status. /// /// \retval 0 Success /// /// \retval -OCB_INVALID_ARGUMENT_TIMER Causes include illegal timer /// numbers and illegal or unrepresntable timeouts. // Note that OCB_TIMER_FREQUENCY_HZ is in the range of 1-2 MHz. int ocb_timer_reset(int timer, int auto_reload, int timeout_ns) { ocb_otrn_t otr; int ticks; //printk("ocb_timer_reset(%d, %d, %d)\n", // timer, auto_reload, timeout_ns); if (timeout_ns != 0) { ticks = MAX(1, timeout_ns / (1000000000 / OCB_TIMER_FREQUENCY_HZ)); } else { ticks = 0; } if (SSX_ERROR_CHECK_API) { SSX_ERROR_IF((timer < 0) || (timer >= OCB_TIMERS) || (timeout_ns < 0) || (ticks != ((uint16_t)ticks)), OCB_INVALID_ARGUMENT_TIMER); } otr.value = 0; otr.fields.timeout = 1; otr.fields.control = 1; otr.fields.auto_reload = (auto_reload != 0); otr.fields.timer = ticks; out32(OCB_OTRN(timer), otr.value); return 0; } /// Set up an OCB timer and interrupt handler /// /// \param timer A valid OCB timer index /// /// \param auto_reload A non-0 value indicates to run the timer in auto-reload /// mode. /// /// \param timeout_ns The timeout specified in nanoseconds. The actual timeout /// will rounded down to the underlying timer tick, with a minimum 1 tick /// timeout. However if the \a timeout_ns argument is explicity 0, then the /// timer wil be initialized with a 0 timeout, effectively disabling the /// timer. /// /// \param handler The interrupt handler for the timer interrupt /// /// \param arg The private argument of the interrupt handler /// /// \param priority The SSX/PPC405 interrupt priority to assign to the /// interrupt. /// /// This API sets up and starts the timer and unmasks the timer /// interrupt. Once set up, the timer can be reset using ocb_timer_reset(). As /// a fine point of the specification, if a timer interrupt is already pending /// when this API is invoked then that interrupt will be cleared. Only the /// next interrupt (corresponding to the new programming) will be serviced by /// the newly installed handler. /// /// Note that the interrupt handler is responsible for clearing the timer /// interrupt status. An API ocb_timer_status_clear() is available for this. /// /// \retval 0 Success /// /// \retval -OCB_INVALID_ARGUMENT_TIMER Causes include illegal timer /// numbers and illegal or unrepresntable timeouts. /// /// Other errors may be returned by the underlying calls to SSX routines that /// set up interrupts. int ocb_timer_setup(int timer, int auto_reload, int timeout_ns, SsxIrqHandler handler, void* arg, int priority) { int rc; //printk("ocb_timer_setup(%d, %d, %d, %p, %p, %d)\n", // timer, auto_reload, timeout_ns, // handler, arg, priority); ssx_irq_disable(OCCHW_IRQ_OCC_TIMER0 + timer); ssx_irq_setup(OCCHW_IRQ_OCC_TIMER0 + timer, SSX_IRQ_POLARITY_ACTIVE_HIGH, SSX_IRQ_TRIGGER_LEVEL_SENSITIVE); ssx_irq_handler_set(OCCHW_IRQ_OCC_TIMER0 + timer, handler, arg, priority); rc = ocb_timer_reset(timer, auto_reload, timeout_ns); ssx_irq_enable(OCCHW_IRQ_OCC_TIMER0 + timer); return rc; } /// Generate an core interrupt via the PSI Host Bridge /// /// Setting OCB_OCCMISC.core_ext_int to 1 causes a wire to pulse to the PSI /// Host Bridge to allow the presentation of an external interrupt to a core /// thread. The core thread to be interrupted is controlled by the XIVR - OCC /// register, SCOM 02010916. Normally the hypervisor will set up the PSI Host /// Bridge. This procedure allows OCC to send an interrupt to the hypervisor. /// /// \retval 0 Success // The interrupt is generated by causing a 0->1 transation on // OCB_OCCMISC.core_ext_intr. This is implemented here using the WAND/WOR // forms of the register. int ocb_core_interrupt() { ocb_occmisc_t oo; oo.value = 0; oo.fields.core_ext_intr = 1; out32(OCB_OCCMISC_CLR, oo.value); out32(OCB_OCCMISC_OR, oo.value); return 0; } /// Procedure to setup a linear window on an indirect channel /// /// /// Since the linear window access is restricted to the SRAM region of /// OCI space, Bits 0:4 of the base parameter are don't care and will be /// overwritten with b'11000' /// /// /// \todo double check SRAM region restriction /// /// \param channel The indirect channel to use, in the range 0..2. /// /// \param base The 32-bit PowerBus base address where the block starts. This /// address must be aligned to the \a log_size. /// /// \param log_size The base 2 logarithm of the block size, in bytes. The /// minimum size is 8B (2**3), the maximum size is 32KB (2**15) /// /// /// \retval 0 Success /// /// \retval OCB_INVALID_ARGUMENT_LW_INIT One or more of the parameter /// restrictions were violated. /// /// \retval OCB_SCOM_ERROR An attempt to write a PBA SCOM register to set up /// the BARs produced a non-zero return code. int ocb_linear_window_initialize(int channel, uint32_t base, int log_size) { uint32_t mask ; ocb_ocblwcrn_t ocblwcrn; ocb_ocblwsbrn_t ocblwsbrn; // create mask for checking mask = (0x1ull << log_size) - 1; if (SSX_ERROR_CHECK_API) { SSX_ERROR_IF((channel < 0) || (channel > 3) || (log_size < OCB_LW_LOG_SIZE_MIN) || (log_size > OCB_LW_LOG_SIZE_MAX) || ((base & mask) != 0), OCB_INVALID_ARGUMENT_LW_INIT); } // now invert mask for use in ocb linear window setup mask = ~mask; // Enable LW mode ocblwcrn.fields.linear_window_enable = 1; // Select bits(12:28) of OCI addr for the LW bar ocblwcrn.fields.linear_window_bar = (base >> 3) & 0x1FFFF; ocblwcrn.fields.linear_window_mask = (mask >> 3) & 0xFFF; out32(OCB_OCBLWCRN(channel), ocblwcrn.value); // Configure LW region for SRAM access ocblwsbrn.fields.linear_window_region = 7; // Select bits(5:11) of OCI addr for the LW base ocblwsbrn.fields.linear_window_base = (base >> 20) & 0x7F; out32(OCB_OCBLWSBRN(channel), ocblwsbrn.value); return 0 ; } /// Procedure to disable a linear window on an indirect channel /// /// This procedure will disable the linear window while maintaining /// the linear_window_bar and linear_window_mask settings /// /// \param channel The indirect channel to disable, in the range 0..2. /// /// \retval 0 Success /// /// \retval OCB_INVALID_ARGUMENT_LW_DISABLE One or more of the parameter /// restrictions were violated. /// int ocb_linear_window_disable(int channel) { ocb_ocblwcrn_t ocblwcrn; if (SSX_ERROR_CHECK_API) { SSX_ERROR_IF((channel < 0) || (channel > 3), OCB_INVALID_ARGUMENT_LW_DISABLE); } ocblwcrn.value = in32(OCB_OCBLWCRN(channel)); // Disable LW mode ocblwcrn.fields.linear_window_enable = 0; out32(OCB_OCBLWCRN(channel), ocblwcrn.value); return 0 ; } /// Procedure for setting up untrusted mode for an indirect channel /// /// Note that the OCC FW is expected to enable the channel for FSP /// access. As an untrusted master, the FSP cannot configure this /// in a chip running in trusted mode. The SBE is considered a trusted /// master. /// /// /// \param channel The indirect channel to use, in the range 0..2 /// Note that this bit is not used for indirect channel 3. /// /// /// \param allow_untrusted Enable untrusted PIB masters /// access to the indirect channel being configured. If allow_untrusted is /// not enabled and the chip is running in trusted mode, then any untrusted /// PIB master will get an offline return code when attempting to write /// the indirect channel. 0 = Disable, 1 = Enable /// /// \retval 0 Success /// /// \retval OCB_INVALID_ARGUMENT_UNTRUST One or more of the parameter /// restrictions were violated. /// //NOTE: The OCBICR register seems to have gone away in P9 and we didn't ever // call this function in P8 so I'm removing this function for now. (grm) #if 0 int ocb_allow_untrusted_initialize(int channel, int allow_untrusted) { ocb_ocbicrn_t ocbicrn; if (SSX_ERROR_CHECK_API) { SSX_ERROR_IF((channel < 0) || (channel > 2) || (allow_untrusted < 0) || (allow_untrusted > 1), OCB_INVALID_ARGUMENT_UNTRUST); } // Configure allow_unsecure_pib_masters bit ocbicrn.fields.allow_unsecure_pib_masters = allow_untrusted; out32(OCB_OCBICRN(channel), ocbicrn.value); return 0 ; } #endif