summaryrefslogtreecommitdiff
path: root/chip/npcx/ps2.c
diff options
context:
space:
mode:
authorCHLin <CHLIN56@nuvoton.com>2019-12-25 16:29:14 +0800
committerCommit Bot <commit-bot@chromium.org>2020-01-30 20:23:49 +0000
commitd79ee1cba05d36d568ed6f30ba5dadb45fd57681 (patch)
treeb2fcfdfba2da96fe0efc8daf9ba8b605d40f501b /chip/npcx/ps2.c
parent85902285e20f4e6d8390d944b48ccb96ee252f40 (diff)
downloadchrome-ec-d79ee1cba05d36d568ed6f30ba5dadb45fd57681.tar.gz
npcx: Add driver support for PS/2 interface
Morphius connects the trackpoint device to EC via the PS/2 interface. To support it, we implemented the chip level PS/2 driver in this CL. The PS/2 driver can be used on all series of NPCX EC chips (NPCX5/7). BUG=b:145575366 BRANCH=none TEST=No error for "make buildall" TEST=Apply this and related CLs, connect npcx5/npcx7 EVBs to standard PS/2 keyboards and PS/2 device emulator with different channels. Verify that the PS/2 write/read transaction can keep working for several hours without issue. Change-Id: I5bae313db2d697999c2da5cf33478be2da754b8c Signed-off-by: CHLin <CHLIN56@nuvoton.com> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1982302 Tested-by: CH Lin <chlin56@nuvoton.com> Commit-Queue: Edward Hill <ecgh@chromium.org> Auto-Submit: CH Lin <chlin56@nuvoton.com> Reviewed-by: Edward Hill <ecgh@chromium.org>
Diffstat (limited to 'chip/npcx/ps2.c')
-rw-r--r--chip/npcx/ps2.c366
1 files changed, 366 insertions, 0 deletions
diff --git a/chip/npcx/ps2.c b/chip/npcx/ps2.c
new file mode 100644
index 0000000000..c470bbe68d
--- /dev/null
+++ b/chip/npcx/ps2.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2019 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* PS/2 module for Chrome EC */
+#include "atomic.h"
+#include "clock.h"
+#include "console.h"
+#include "hooks.h"
+#include "gpio.h"
+#include "ps2_chip.h"
+#include "task.h"
+#include "registers.h"
+#include "timer.h"
+#include "util.h"
+
+#define CPRINTS(format, args...) cprints(CC_PS2, format, ## args)
+#define CPRINTF(format, args...) cprintf(CC_PS2, format, ## args)
+
+#if !(DEBUG_PS2)
+#define DEBUG_CPRINTS(...)
+#define DEBUG_CPRINTF(...)
+#else
+#define DEBUG_CPRINTS(format, args...) cprints(CC_PS2, format, ## args)
+#define DEBUG_CPRINTF(format, args...) cprintf(CC_PS2, format, ## args)
+#endif
+
+/*
+ * Set WDAT3-0 and clear CLK3-0 in the PSOSIG register to
+ * reset the shift mechanism.
+ */
+#define PS2_SHIFT_MECH_RESET 0x47
+
+#define PS2_TRANSACTION_TIMEOUT (20 * MSEC)
+#define PS2_BUSY_RETRY 10
+
+enum ps2_input_debounce_cycle {
+ PS2_IDB_1_CYCLE,
+ PS2_IDB_2_CYCLE,
+ PS2_IDB_4_CYCLE,
+ PS2_IDB_8_CYCLE,
+ PS2_IDB_16_CYCLE,
+ PS2_IDB_32_CYCLE,
+};
+
+enum ps2_opr_mode {
+ PS2_TX_MODE,
+ PS2_RX_MODE,
+};
+
+struct ps2_data {
+ /* PS/2 module operation mode */
+ uint8_t opr_mode;
+ /*
+ * The callback function to process data received from PS/2 device.
+ * Note: this is called in the PS/2 interrupt handler
+ */
+ void (*rx_handler_cb)(uint8_t data);
+};
+static struct ps2_data ps2_ch_data[NPCX_PS2_CH_COUNT] = {
+ [0 ... (NPCX_PS2_CH_COUNT - 1)] = { PS2_RX_MODE, NULL }
+};
+
+/*
+ * Bitmap to record the enabled PS/2 channel by upper layer.
+ * Only bit[7 and bit[5:3] are used
+ * (i.e. the bit position of CLK3-0 in the PS2_PSOSIG register)
+ */
+static uint32_t channel_enabled_mask;
+static struct mutex ps2_lock;
+static volatile task_id_t task_waiting = TASK_ID_INVALID;
+
+static void ps2_init(void)
+{
+ /* Disable the power down bit of PS/2 */
+ clock_enable_peripheral(CGC_OFFSET_PS2, CGC_PS2_MASK,
+ CGC_MODE_RUN | CGC_MODE_SLEEP);
+
+ /* Disable shift mechanism and configure PS/2 to received mode. */
+ NPCX_PS2_PSCON = 0x0;
+ /* Set WDAT3-0 and clear CLK3-0 before enabling shift mechanism */
+ NPCX_PS2_PSOSIG = PS2_SHIFT_MECH_RESET;
+
+ /*
+ * PS/2 interrupt enable register
+ * [0] - : SOTIE = 1: Start Of Transaction Interrupt Enable
+ * [1] - : EOTIE = 1: End Of Transaction Interrupt Enable
+ * [4] - : WUE = 1: Wake-Up Enable
+ * [7] - : CLK_SEL = 1: Select Free-Run clock as the basic clock
+ */
+ NPCX_PS2_PSIEN = BIT(NPCX_PS2_PSIEN_SOTIE) |
+ BIT(NPCX_PS2_PSIEN_EOTIE) |
+ BIT(NPCX_PS2_PSIEN_PS2_WUE) |
+ BIT(NPCX_PS2_PSIEN_PS2_CLK_SEL);
+
+ /* Enable weak internal pull-up */
+ SET_BIT(NPCX_PS2_PSCON, NPCX_PS2_PSCON_WPUED);
+ /* Enable shift mechanism */
+ SET_BIT(NPCX_PS2_PSCON, NPCX_PS2_PSCON_EN);
+
+ /* Configure pins from GPIOs to PS/2 interface */
+ gpio_config_module(MODULE_PS2, 1);
+ task_enable_irq(NPCX_IRQ_PS2);
+}
+DECLARE_HOOK(HOOK_INIT, ps2_init, HOOK_PRIO_DEFAULT);
+
+void ps2_enable_channel(int channel, int enable,
+ void (*callback)(uint8_t data))
+{
+ if (channel >= NPCX_PS2_CH_COUNT) {
+ CPRINTS("Err:PS/2 CH exceed %d", NPCX_PS2_CH_COUNT);
+ return;
+ }
+
+ /*
+ * Disable the interrupt during changing the enabled channel mask to
+ * prevent from preemption
+ */
+ interrupt_disable();
+ if (enable) {
+ ps2_ch_data[channel].rx_handler_cb = callback;
+ channel_enabled_mask |= BIT(NPCX_PS2_PSOSIG_CLK(channel));
+ /* Enable the relevant channel clock */
+ SET_BIT(NPCX_PS2_PSOSIG, NPCX_PS2_PSOSIG_CLK(channel));
+ } else {
+ channel_enabled_mask &= ~BIT(NPCX_PS2_PSOSIG_CLK(channel));
+ /* Disable the relevant channel clock */
+ CLEAR_BIT(NPCX_PS2_PSOSIG, NPCX_PS2_PSOSIG_CLK(channel));
+ ps2_ch_data[channel].rx_handler_cb = NULL;
+ }
+ interrupt_enable();
+}
+
+/* Check if the shift mechanism is busy */
+static int ps2_is_busy(void)
+{
+ /*
+ * The driver pulls the CLK for non-active channels to low when Start
+ * bit is detected and pull the CLK of the active channel low after
+ * Stop bit detected. The EOT bit is set when Stop bit is detected,
+ * but both SOT and EOT are cleared when all CLKs are pull low
+ * (due to Shift Mechanism is reset)
+ */
+ return (IS_BIT_SET(NPCX_PS2_PSTAT, NPCX_PS2_PSTAT_SOT) |
+ IS_BIT_SET(NPCX_PS2_PSTAT, NPCX_PS2_PSTAT_EOT)) ? 1 : 0;
+}
+
+int ps2_transmit_byte(int channel, uint8_t data)
+{
+ int event;
+
+ uint8_t busy_retry = PS2_BUSY_RETRY;
+
+ if (channel >= NPCX_PS2_CH_COUNT) {
+ CPRINTS("Err:PS/2 CH exceed %d", NPCX_PS2_CH_COUNT);
+ return EC_ERROR_INVAL;
+ }
+
+ if (!(BIT(NPCX_PS2_PSOSIG_CLK(channel)) & channel_enabled_mask)) {
+ CPRINTS("Err: PS/2 Tx w/o enabling CH");
+ return EC_ERROR_INVAL;
+ }
+
+ mutex_lock(&ps2_lock);
+ while (ps2_is_busy()) {
+ usleep(PS2_TRANSACTION_TIMEOUT);
+ if (busy_retry == 0) {
+ mutex_unlock(&ps2_lock);
+ return EC_ERROR_BUSY;
+ }
+ busy_retry--;
+ }
+
+ task_waiting = task_get_current();
+ ps2_ch_data[channel].opr_mode = PS2_TX_MODE;
+
+ /* Set PS/2 in transmit mode */
+ SET_BIT(NPCX_PS2_PSCON, NPCX_PS2_PSCON_XMT);
+ /* Enable Start Of Transaction interrupt */
+ SET_BIT(NPCX_PS2_PSIEN, NPCX_PS2_PSIEN_SOTIE);
+
+ /* Reset the shift mechanism */
+ NPCX_PS2_PSOSIG = PS2_SHIFT_MECH_RESET;
+ /* Inhibit communication should last at least 100 micro-seconds */
+ udelay(100);
+
+ /* Write the data to be transmitted */
+ NPCX_PS2_PSDAT = data;
+ /* Apply the Request-to-send */
+ CLEAR_BIT(NPCX_PS2_PSOSIG, NPCX_PS2_PSOSIG_WDAT(channel));
+ SET_BIT(NPCX_PS2_PSOSIG, NPCX_PS2_PSOSIG_CLK(channel));
+
+ /* Wait for interrupt */
+ event = task_wait_event_mask(TASK_EVENT_PS2_DONE,
+ PS2_TRANSACTION_TIMEOUT);
+ task_waiting = TASK_ID_INVALID;
+
+ if (event == TASK_EVENT_TIMER) {
+ task_disable_irq(NPCX_IRQ_PS2);
+ CPRINTS("PS/2 Tx timeout");
+ /* Reset the shift mechanism */
+ NPCX_PS2_PSOSIG = PS2_SHIFT_MECH_RESET;
+ /* Change the PS/2 module to receive mode */
+ CLEAR_BIT(NPCX_PS2_PSCON, NPCX_PS2_PSCON_XMT);
+ /* Restore the channel to Receive mode */
+ ps2_ch_data[channel].opr_mode = PS2_RX_MODE;
+ /*
+ * Restore the enabled channel according to channel_enabled_mask
+ */
+ NPCX_PS2_PSOSIG |= channel_enabled_mask;
+ task_enable_irq(NPCX_IRQ_PS2);
+ }
+ mutex_unlock(&ps2_lock);
+
+ DEBUG_CPRINTF("Evt:0x%08x\n", event);
+ return (event == TASK_EVENT_PS2_DONE) ? EC_SUCCESS : EC_ERROR_TIMEOUT;
+
+}
+
+static void ps2_stop_inactive_ch_clk(uint8_t active_ch)
+{
+ uint8_t mask;
+
+ mask = ~NPCX_PS2_PSOSIG_CLK_MASK_ALL |
+ BIT(NPCX_PS2_PSOSIG_CLK(active_ch));
+ NPCX_PS2_PSOSIG &= mask;
+
+}
+
+static int ps2_is_rx_error(uint8_t ch)
+{
+ uint8_t status;
+
+ status = NPCX_PS2_PSTAT &
+ (BIT(NPCX_PS2_PSTAT_PERR) |
+ BIT(NPCX_PS2_PSTAT_RFERR));
+ if (status) {
+
+ if (status & BIT(NPCX_PS2_PSTAT_PERR))
+ CPRINTF("PS2 CH %d RX parity error\n", ch);
+ if (status & BIT(NPCX_PS2_PSTAT_RFERR))
+ CPRINTF("PS2 CH %d RX Frame error\n", ch);
+ return 1;
+ } else
+ return 0;
+}
+
+void ps2_int_handler(void)
+{
+ uint8_t active_ch;
+
+ DEBUG_CPRINTS("PS2 INT");
+ /*
+ * ACH = 1 : CHannel 0
+ * ACH = 2 : CHannel 1
+ * ACH = 4 : CHannel 2
+ * ACH = 5 : CHannel 3
+ */
+ active_ch = GET_FIELD(NPCX_PS2_PSTAT, NPCX_PS2_PSTAT_ACH);
+ active_ch = active_ch > 2 ? (active_ch - 2) : (active_ch - 1);
+ DEBUG_CPRINTF("ACH:%0d-", active_ch);
+
+ /*
+ * Inhibit PS/2 transaction of the other non-active channels by
+ * pulling down the clock signal
+ */
+ ps2_stop_inactive_ch_clk(active_ch);
+
+ /* PS/2 Start of Transaction */
+ if (IS_BIT_SET(NPCX_PS2_PSTAT, NPCX_PS2_PSTAT_SOT) &&
+ IS_BIT_SET(NPCX_PS2_PSIEN, NPCX_PS2_PSIEN_SOTIE)) {
+ DEBUG_CPRINTF("SOT-");
+ /*
+ * Once set, SOT is not cleared until the shift mechanism
+ * is reset. Therefore, SOTIE should be cleared on the
+ * first occurrence of an SOT interrupt.
+ */
+ CLEAR_BIT(NPCX_PS2_PSIEN, NPCX_PS2_PSIEN_SOTIE);
+ /* PS/2 End of Transaction */
+ } else if (IS_BIT_SET(NPCX_PS2_PSTAT, NPCX_PS2_PSTAT_EOT)) {
+ DEBUG_CPRINTF("EOT-");
+ CLEAR_BIT(NPCX_PS2_PSIEN, NPCX_PS2_PSIEN_EOTIE);
+
+ /*
+ * Clear the CLK of active channel to reset
+ * the shift mechanism
+ */
+ CLEAR_BIT(NPCX_PS2_PSOSIG, NPCX_PS2_PSOSIG_CLK(active_ch));
+
+ if (ps2_ch_data[active_ch].opr_mode == PS2_TX_MODE) {
+ /* Change the PS/2 module to receive mode */
+ CLEAR_BIT(NPCX_PS2_PSCON, NPCX_PS2_PSCON_XMT);
+ ps2_ch_data[active_ch].opr_mode = PS2_RX_MODE;
+ task_set_event(task_waiting, TASK_EVENT_PS2_DONE, 0);
+ } else {
+ if (!ps2_is_rx_error(active_ch)) {
+ uint8_t data_read = NPCX_PS2_PSDAT;
+ struct ps2_data *ps2_ptr =
+ &ps2_ch_data[active_ch];
+
+ DEBUG_CPRINTF("Recv:0x%02x", data_read);
+ if (ps2_ptr->rx_handler_cb)
+ ps2_ptr->rx_handler_cb(data_read);
+ }
+ }
+
+ /* Restore the enabled channel */
+ NPCX_PS2_PSOSIG |= channel_enabled_mask;
+ /*
+ * Re-enable the Start Of Transaction interrupt when
+ * the shift mechanism is reset
+ */
+ SET_BIT(NPCX_PS2_PSIEN, NPCX_PS2_PSIEN_SOTIE);
+ SET_BIT(NPCX_PS2_PSIEN, NPCX_PS2_PSIEN_EOTIE);
+ }
+ DEBUG_CPRINTF("\n");
+
+}
+DECLARE_IRQ(NPCX_IRQ_PS2, ps2_int_handler, 5);
+
+#ifdef CONFIG_CMD_PS2
+static int command_ps2ench(int argc, char **argv)
+{
+ uint8_t ch;
+ uint8_t enable;
+ char *e;
+
+ ch = strtoi(argv[1], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM2;
+
+ enable = strtoi(argv[2], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM2;
+ if (enable)
+ ps2_enable_channel(ch, 1, NULL);
+ else
+ ps2_enable_channel(ch, 0, NULL);
+
+ return 0;
+}
+DECLARE_CONSOLE_COMMAND(ps2ench, command_ps2ench,
+ "ps2_ench channel 1|0",
+ "Enable/Disable PS/2 channel");
+
+static int command_ps2write(int argc, char **argv)
+{
+ uint8_t ch, data;
+ char *e;
+
+ ch = strtoi(argv[1], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM2;
+ data = strtoi(argv[2], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM2;
+
+ ps2_transmit_byte(ch, data);
+ return 0;
+}
+DECLARE_CONSOLE_COMMAND(ps2write, command_ps2write,
+ "ps2_write channel data",
+ "Write data byte to PS/2 channel ");
+#endif