/*      $NetBSD: eeprom.c,v 1.35 2024/12/20 23:52:00 tsutsui Exp $      */

/*-
* Copyright (c) 1996 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Gordon W. Ross.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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.
*/

/*
* Access functions for the EEPROM (Electrically Eraseable PROM)
* The main reason for the existence of this module is to
* handle the painful task of updating the EEPROM contents.
* After a write, it must not be touched for 10 milliseconds.
* (See the Sun-3 Architecture Manual sec. 5.9)
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: eeprom.c,v 1.35 2024/12/20 23:52:00 tsutsui Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/buf.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/kernel.h>

#include <machine/autoconf.h>
#include <machine/idprom.h>
#include <machine/eeprom.h>

#ifndef EEPROM_SIZE
#define EEPROM_SIZE 0x800
#endif

struct eeprom *eeprom_copy;     /* soft copy. */

static int ee_update(int off, int cnt);

static uint8_t *eeprom_va; /* mapping to actual device */
static int ee_size;     /* size of usable part. */

static int ee_busy, ee_want; /* serialization */

static int  eeprom_match(device_t, cfdata_t, void *);
static void eeprom_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(eeprom, 0,
   eeprom_match, eeprom_attach, NULL, NULL);

static int
eeprom_match(device_t parent, cfdata_t cf, void *args)
{
       struct confargs *ca = args;

       /* This driver only supports one instance. */
       if (eeprom_va != NULL)
               return 0;

       if (bus_peek(ca->ca_bustype, ca->ca_paddr, 1) == -1)
               return 0;

       return 1;
}

static void
eeprom_attach(device_t parent, device_t self, void *args)
{
       struct confargs *ca = args;
       uint8_t *src, *dst, *lim;

       aprint_normal("\n");
#ifdef  DIAGNOSTIC
       if (sizeof(struct eeprom) != EEPROM_SIZE)
               panic("eeprom struct wrong");
#endif

       ee_size = EEPROM_SIZE;
       eeprom_va = bus_mapin(ca->ca_bustype, ca->ca_paddr, ee_size);
       if (eeprom_va == NULL)
               panic("%s: can't map va", __func__);

       /* Keep a "soft" copy of the EEPROM to make access simpler. */
       eeprom_copy = kmem_alloc(ee_size, KM_SLEEP);

       /*
        * On the 3/80, do not touch the last 40 bytes!
        * Writes there are ignored, reads show zero.
        * Reduce ee_size and clear the last part of the
        * soft copy.  Note: ee_update obeys ee_size.
        */
       if (cpu_machine_id == ID_SUN3X_80)
               ee_size -= 40;

       /* Do only byte access in the EEPROM. */
       src = eeprom_va;
       dst = (uint8_t *)eeprom_copy;
       lim = dst + ee_size;

       do {
               *dst++ = *src++;
       } while (dst < lim);

       if (ee_size < EEPROM_SIZE) {
               /* Clear out the last part. */
               memset(dst, 0, (EEPROM_SIZE - ee_size));
       }
}


/* Take the lock. */
static int
ee_take(void)
{
       int error = 0;

       while (ee_busy) {
               ee_want = 1;
               error = tsleep(&ee_busy, PZERO | PCATCH, "eeprom", 0);
               ee_want = 0;
               if (error)      /* interrupted */
                       return error;
       }
       ee_busy = 1;
       return error;
}

/* Give the lock. */
static void
ee_give(void)
{
       ee_busy = 0;
       if (ee_want) {
               ee_want = 0;
               wakeup(&ee_busy);
       }
}

/*
* This is called by mem.c to handle /dev/eeprom
*/
int
eeprom_uio(struct uio *uio)
{
       int cnt, error;
       int off;        /* NOT off_t */
       uint8_t *va;

       if (eeprom_copy == NULL)
               return ENXIO;

       off = uio->uio_offset;
       if ((off < 0) || (off > EEPROM_SIZE))
               return EFAULT;

       cnt = uimin(uio->uio_resid, (EEPROM_SIZE - off));
       if (cnt == 0)
               return 0;       /* EOF */

       va = ((uint8_t *)eeprom_copy) + off;
       error = uiomove(va, (int)cnt, uio);

       /* If we wrote the rambuf, update the H/W. */
       if (error == 0 && (uio->uio_rw != UIO_READ)) {
               error = ee_take();
               if (error == 0)
                       error = ee_update(off, cnt);
               ee_give();
       }

       return error;
}

/*
* Update the EEPROM from the soft copy.
* Other than the attach function, this is
* the ONLY place we touch the EEPROM H/W.
*/
static int
ee_update(int off, int cnt)
{
       volatile uint8_t *ep;
       uint8_t *bp;
       int errcnt;

       if (eeprom_va == NULL)
               return (ENXIO);

       /*
        * This check is NOT redundant with the one in
        * eeprom_uio above if we are on a 3/80 where
        * ee_size < EEPROM_SIZE
        */
       if (cnt > (ee_size - off))
               cnt = (ee_size - off);

       bp = ((uint8_t *)eeprom_copy) + off;
       ep = eeprom_va + off;
       errcnt = 0;

       while (cnt > 0) {
               /*
                * DO NOT WRITE IT UNLESS WE HAVE TO because the
                * EEPROM has a limited number of write cycles.
                * After some number of writes it just fails!
                */
               if (*ep != *bp) {
                       *ep = *bp;
                       /*
                        * We have written the EEPROM, so now we must
                        * sleep for at least 10 milliseconds while
                        * holding the lock to prevent all access to
                        * the EEPROM while it recovers.
                        */
                       (void)tsleep(eeprom_va, PZERO - 1, "eeprom", hz / 50);
               }
               /* Make sure the write worked. */
               if (*ep != *bp)
                       errcnt++;
               ep++;
               bp++;
               cnt--;
       }
       return errcnt ? EIO : 0;
}