Re: [PATCH v2 2/8] mmc: bcm2835: Add new driver for the sdhost controller.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




Hi Gerd,

first of all here my wishlist for the next round of this driver:
* compile test the series with ARM and ARM64
* add me in CC for all patches of the series
* run checkpatch.pl before submission
* apply the patch series to your cgit repo

> Gerd Hoffmann <kraxel@xxxxxxxxxx> hat am 3. Februar 2017 um 13:11 geschrieben:
> 
> 
> From: Eric Anholt <eric@xxxxxxxxxx>
> 
> The 2835 has two SD controllers: The Arasan sdhci controller (supported
> by the iproc driver) and a custom sdhost controller.  This patch adds a
> driver for the latter.
> 
> The sdhci controller supports both sdcard and sdio.  The sdhost
> controller supports the sdcard only, but has better performance.  Also

Sorry, for the confusion. I was wrong. According to the registers the SDHOST should also support SDIO. It's a feature we could implement later.

> note that the rpi3 has sdio wifi, so driving the sdcard with the sdhost
> controller allows to use the sdhci controller for wifi support.
> 
> The configuration is done by devicetree via pin muxing.  Both SD
> controller are available on the same pins (2 pin groups = pin 22 to 27 +
> pin 48 to 53).  So it's possible to use both SD controllers at the same
> time with different pin groups.
> 
> The code was originally written by Phil Elwell in the downstream
> Rasbperry Pi tree, and I did a major cleanup on it (+319, -707 lines
> out of the original 2055) for inclusion.

I think it would be helpful to known the downstream commit, because this commit [1]
doesn't seem to be included.

[1] - https://github.com/raspberrypi/linux/commit/ea4b1c5c2ddbb6caba43ab9b0103542a4ca7e1f0

I've found and fixed a lot of issues in this version and i think it would be better if i send you the patches for squashing them all together. Here is the preview for my patch series:

Stefan Wahren (15):
  mmc: bcm2835: Add missing include for threaded irq
  mmc: bcm2835: Remove CMD_DALLY_US
  mmc: bcm2835: Fix pio_timeout handling
  mmc: bcm2835: Remove unnecessary return in bcm2835_data_irq
  mmc: bcm2835: Handle error cases during probe
  mmc: bcm2835: Print clk_max as decimal
  mmc: bcm2835: Downrate message in case of PIO fallback
  mmc: bcm2835: Don't unveil the data pointer
  mmc: bcm2835: remove unused host members
  mmc: bcm2835: Avoid unnecessary linebreaks
  mmc: bcm2835: Add leading zero to register dumps
  mmc: bcm2835: Align struct members with tabs
  mmc: bcm2835: Rearrange bcm2835_finish_request()
  mmc: bcm2835: Rearrange bcm2835_dma_complete_work()
  mmc: bcm2835: Rename Kconfig switch

I'll send it after my tests. In the following review i will mention only the issues which aren't fixed in my patch series.

> diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c
> new file mode 100644
> index 0000000..5ea8bd6
> --- /dev/null
> +++ b/drivers/mmc/host/bcm2835.c
> @@ -0,0 +1,1521 @@
> +/*
> + * bcm2835 sdhost driver.
> + *
> + * The 2835 has two SD controllers: The Arasan sdhci controller
> + * (supported by the iproc driver) and a custom sdhost controller
> + * (supported by this driver).
> + *
> + * The sdhci controller supports both sdcard and sdio.  The sdhost
> + * controller supports the sdcard only, but has better performance.
> + * Also note that the rpi3 has sdio wifi, so driving the sdcard with
> + * the sdhost controller allows to use the sdhci controller for wifi
> + * support.
> + *
> + * The configuration is done by devicetree via pin muxing.  Both
> + * SD controller are available on the same pins (2 pin groups = pin 22
> + * to 27 + pin 48 to 53).  So it's possible to use both SD controllers
> + * at the same time with different pin groups.
> + *
> + * Author:      Phil Elwell <phil@xxxxxxxxxxxxxxx>
> + *              Copyright (C) 2015-2016 Raspberry Pi (Trading) Ltd.
> + *
> + * Based on
> + *  mmc-bcm2835.c by Gellert Weisz
> + * which is, in turn, based on
> + *  sdhci-bcm2708.c by Broadcom
> + *  sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko
> + *  sdhci.c and sdhci-pci.c by Pierre Ossman
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/highmem.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/scatterlist.h>
> +#include <linux/time.h>
> +#include <linux/workqueue.h>
> +
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sd.h>
> +
> +#define SDCMD  0x00 /* Command to SD card              - 16 R/W */
> +#define SDARG  0x04 /* Argument to SD card             - 32 R/W */
> +#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */
> +#define SDCDIV 0x0c /* Start value for clock divider   - 11 R/W */
> +#define SDRSP0 0x10 /* SD card response (31:0)         - 32 R   */
> +#define SDRSP1 0x14 /* SD card response (63:32)        - 32 R   */
> +#define SDRSP2 0x18 /* SD card response (95:64)        - 32 R   */
> +#define SDRSP3 0x1c /* SD card response (127:96)       - 32 R   */
> +#define SDHSTS 0x20 /* SD host status                  - 11 R   */
> +#define SDVDD  0x30 /* SD card power control           -  1 R/W */
> +#define SDEDM  0x34 /* Emergency Debug Mode            - 13 R/W */
> +#define SDHCFG 0x38 /* Host configuration              -  2 R/W */
> +#define SDHBCT 0x3c /* Host byte count (debug)         - 32 R/W */
> +#define SDDATA 0x40 /* Data to/from SD card            - 32 R/W */
> +#define SDHBLC 0x50 /* Host block count (SDIO/SDHC)    -  9 R/W */
> +
> +#define SDCMD_NEW_FLAG			0x8000
> +#define SDCMD_FAIL_FLAG			0x4000
> +#define SDCMD_BUSYWAIT			0x800
> +#define SDCMD_NO_RESPONSE		0x400
> +#define SDCMD_LONG_RESPONSE		0x200
> +#define SDCMD_WRITE_CMD			0x80
> +#define SDCMD_READ_CMD			0x40
> +#define SDCMD_CMD_MASK			0x3f
> +
> +#define SDCDIV_MAX_CDIV			0x7ff
> +
> +#define SDHSTS_BUSY_IRPT		0x400
> +#define SDHSTS_BLOCK_IRPT		0x200
> +#define SDHSTS_SDIO_IRPT		0x100
> +#define SDHSTS_REW_TIME_OUT		0x80
> +#define SDHSTS_CMD_TIME_OUT		0x40
> +#define SDHSTS_CRC16_ERROR		0x20
> +#define SDHSTS_CRC7_ERROR		0x10
> +#define SDHSTS_FIFO_ERROR		0x08
> +/* Reserved */
> +/* Reserved */
> +#define SDHSTS_DATA_FLAG		0x01
> +
> +#define SDHSTS_TRANSFER_ERROR_MASK	(SDHSTS_CRC7_ERROR | \
> +					 SDHSTS_CRC16_ERROR | \
> +					 SDHSTS_REW_TIME_OUT | \
> +					 SDHSTS_FIFO_ERROR)
> +
> +#define SDHSTS_ERROR_MASK		(SDHSTS_CMD_TIME_OUT | \
> +					 SDHSTS_TRANSFER_ERROR_MASK)
> +
> +#define SDHCFG_BUSY_IRPT_EN	BIT(10)
> +#define SDHCFG_BLOCK_IRPT_EN	BIT(8)
> +#define SDHCFG_SDIO_IRPT_EN	BIT(5)
> +#define SDHCFG_DATA_IRPT_EN	BIT(4)
> +#define SDHCFG_SLOW_CARD	BIT(3)
> +#define SDHCFG_WIDE_EXT_BUS	BIT(2)
> +#define SDHCFG_WIDE_INT_BUS	BIT(1)
> +#define SDHCFG_REL_CMD_LINE	BIT(0)
> +
> +#define SDVDD_POWER_OFF		0
> +#define SDVDD_POWER_ON		1
> +
> +#define SDEDM_FORCE_DATA_MODE	BIT(19)
> +#define SDEDM_CLOCK_PULSE	BIT(20)
> +#define SDEDM_BYPASS		BIT(21)
> +
> +#define SDEDM_WRITE_THRESHOLD_SHIFT	9
> +#define SDEDM_READ_THRESHOLD_SHIFT	14
> +#define SDEDM_THRESHOLD_MASK		0x1f
> +
> +#define SDEDM_FSM_MASK		0xf
> +#define SDEDM_FSM_IDENTMODE	0x0
> +#define SDEDM_FSM_DATAMODE	0x1
> +#define SDEDM_FSM_READDATA	0x2
> +#define SDEDM_FSM_WRITEDATA	0x3
> +#define SDEDM_FSM_READWAIT	0x4
> +#define SDEDM_FSM_READCRC	0x5
> +#define SDEDM_FSM_WRITECRC	0x6
> +#define SDEDM_FSM_WRITEWAIT1	0x7
> +#define SDEDM_FSM_POWERDOWN	0x8
> +#define SDEDM_FSM_POWERUP	0x9
> +#define SDEDM_FSM_WRITESTART1	0xa
> +#define SDEDM_FSM_WRITESTART2	0xb
> +#define SDEDM_FSM_GENPULSES	0xc
> +#define SDEDM_FSM_WRITEWAIT2	0xd
> +#define SDEDM_FSM_STARTPOWDOWN	0xf
> +
> +#define SDDATA_FIFO_WORDS	16
> +
> +#define FIFO_READ_THRESHOLD	4
> +#define FIFO_WRITE_THRESHOLD	4
> +#define SDDATA_FIFO_PIO_BURST	8
> +#define CMD_DALLY_US		1
> +
> +struct bcm2835_host {
> +	spinlock_t		lock;
> +	struct mutex            mutex;

Would be nice to have a comment for the lock and mutex.

> +
> +	void __iomem		*ioaddr;
> +	u32			phys_addr;
> +
> +	struct mmc_host		*mmc;
> +	struct platform_device	*pdev;
> +
> +	u32			pio_timeout;	/* In jiffies */
> +	int			clock;		/* Current clock speed */
> +	unsigned int		max_clk;	/* Max possible freq */
> +	struct work_struct      dma_work;
> +	struct delayed_work     timeout_work;   /* Timer for timeouts */
> +	struct sg_mapping_iter	sg_miter;	/* SG state for PIO */
> +	unsigned int		blocks;		/* remaining PIO blocks */
> +	int			irq;		/* Device IRQ */
> +
> +	u32			ns_per_fifo_word;
> +
> +	/* cached registers */
> +	u32			hcfg;
> +	u32			cdiv;
> +
> +	struct mmc_request	*mrq;		/* Current request */
> +	struct mmc_command	*cmd;		/* Current command */
> +	struct mmc_data		*data;		/* Current data request */
> +	bool			data_complete:1;/* Data finished before cmd */
> +	bool			flush_fifo:1;	/* Drain the fifo when finish */
> +	bool			use_busy:1;	/* Wait for busy interrupt */
> +	bool			use_sbc:1;	/* Send CMD23 */

It would be nice to get the rid off use_busy and use_sbc. Do you see any chance for use_busy?

> +
> +	/* for threaded irq handler */
> +	bool                    irq_block;
> +	bool                    irq_busy;
> +	bool                    irq_data;
> +
> +	/* DMA part */
> +	struct dma_chan		*dma_chan_rx;
> +	struct dma_chan		*dma_chan_tx;
> +	struct dma_chan		*dma_chan;
> +	struct dma_async_tx_descriptor	*dma_desc;
> +	u32			dma_dir;
> +	u32			drain_words;
> +	struct page		*drain_page;
> +	u32			drain_offset;
> +	bool			use_dma;
> +
> +	int	max_delay;	/* maximum length of time spent waiting */
> +	u32	pio_limit;	/* Maximum block count for PIO (0 = DMA) */
> +};
> +
> +static void bcm2835_dumpcmd(struct bcm2835_host *host,
> +			    struct mmc_command *cmd,
> +			    const char *label)
> +{
> +	struct device *dev = &host->pdev->dev;
> +
> +	if (!cmd)
> +		return;
> +
> +	dev_dbg(dev, "%c%s op %d arg 0x%x flags 0x%x - resp %08x %08x %08x %08x, err %d\n",
> +		(cmd == host->cmd) ? '>' : ' ',
> +		label, cmd->opcode, cmd->arg, cmd->flags,
> +		cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3],
> +		cmd->error);
> +}
> +
> +static void bcm2835_dumpregs(struct bcm2835_host *host)
> +{
> +	struct mmc_request *mrq = host->mrq;
> +	struct device *dev = &host->pdev->dev;
> +
> +	if (mrq) {
> +		bcm2835_dumpcmd(host, mrq->sbc, "sbc");
> +		bcm2835_dumpcmd(host, mrq->cmd, "cmd");
> +		if (mrq->data) {
> +			dev_dbg(dev, "data blocks %x blksz %x - err %d\n",
> +				mrq->data->blocks,
> +				mrq->data->blksz,
> +				mrq->data->error);
> +		}
> +		bcm2835_dumpcmd(host, mrq->stop, "stop");
> +	}
> +
> +	dev_dbg(dev, "=========== REGISTER DUMP ===========\n");
> +	dev_dbg(dev, "SDCMD  0x%08x\n", readl(host->ioaddr + SDCMD));
> +	dev_dbg(dev, "SDARG  0x%08x\n", readl(host->ioaddr + SDARG));
> +	dev_dbg(dev, "SDTOUT 0x%08x\n", readl(host->ioaddr + SDTOUT));
> +	dev_dbg(dev, "SDCDIV 0x%08x\n", readl(host->ioaddr + SDCDIV));
> +	dev_dbg(dev, "SDRSP0 0x%08x\n", readl(host->ioaddr + SDRSP0));
> +	dev_dbg(dev, "SDRSP1 0x%08x\n", readl(host->ioaddr + SDRSP1));
> +	dev_dbg(dev, "SDRSP2 0x%08x\n", readl(host->ioaddr + SDRSP2));
> +	dev_dbg(dev, "SDRSP3 0x%08x\n", readl(host->ioaddr + SDRSP3));
> +	dev_dbg(dev, "SDHSTS 0x%08x\n", readl(host->ioaddr + SDHSTS));
> +	dev_dbg(dev, "SDVDD  0x%08x\n", readl(host->ioaddr + SDVDD));
> +	dev_dbg(dev, "SDEDM  0x%08x\n", readl(host->ioaddr + SDEDM));
> +	dev_dbg(dev, "SDHCFG 0x%08x\n", readl(host->ioaddr + SDHCFG));
> +	dev_dbg(dev, "SDHBCT 0x%08x\n", readl(host->ioaddr + SDHBCT));
> +	dev_dbg(dev, "SDHBLC 0x%08x\n", readl(host->ioaddr + SDHBLC));
> +	dev_dbg(dev, "===========================================\n");
> +}
> +
> +static void bcm2835_reset_internal(struct bcm2835_host *host)
> +{
> +	u32 temp;
> +
> +	writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD);
> +	writel(0, host->ioaddr + SDCMD);
> +	writel(0, host->ioaddr + SDARG);
> +	writel(0xf00000, host->ioaddr + SDTOUT);
> +	writel(0, host->ioaddr + SDCDIV);
> +	writel(0x7f8, host->ioaddr + SDHSTS); /* Write 1s to clear */

Any chance to eliminate the bit magic for SDTOUT and SDHSTS?

> +	writel(0, host->ioaddr + SDHCFG);
> +	writel(0, host->ioaddr + SDHBCT);
> +	writel(0, host->ioaddr + SDHBLC);
> +
> +	/* Limit fifo usage due to silicon bug */
> +	temp = readl(host->ioaddr + SDEDM);
> +	temp &= ~((SDEDM_THRESHOLD_MASK << SDEDM_READ_THRESHOLD_SHIFT) |
> +		  (SDEDM_THRESHOLD_MASK << SDEDM_WRITE_THRESHOLD_SHIFT));
> +	temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) |
> +		(FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT);
> +	writel(temp, host->ioaddr + SDEDM);
> +	msleep(20);
> +	writel(SDVDD_POWER_ON, host->ioaddr + SDVDD);
> +	msleep(20);
> +	host->clock = 0;
> +	writel(host->hcfg, host->ioaddr + SDHCFG);
> +	writel(host->cdiv, host->ioaddr + SDCDIV);
> +}
> +
> ...
> +
> +static void bcm2835_finish_data(struct bcm2835_host *host)
> +{
> +	struct device *dev = &host->pdev->dev;
> +	struct mmc_data *data;
> +
> +	data = host->data;
> +
> +	dev_dbg(dev, "finish_data(error %d, stop %d, sbc %d)\n",
> +		data->error, data->stop ? 1 : 0,
> +		host->mrq->sbc ? 1 : 0);
> +
> +	host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN);
> +	writel(host->hcfg, host->ioaddr + SDHCFG);
> +
> +	data->bytes_xfered = data->error ? 0 : (data->blksz * data->blocks);
> +
> +	host->data_complete = true;
> +
> +	if (host->cmd) {
> +		/* Data managed to finish before the
> +		 * command completed. Make sure we do
> +		 * things in the proper order.
> +		 */
> +		dev_dbg(dev, "Finished early - HSTS %x\n",
> +			readl(host->ioaddr + SDHSTS));
> +	} else {
> +		bcm2835_transfer_complete(host);
> +	}
> +}
> +
> +static void bcm2835_finish_command(struct bcm2835_host *host)
> +{
> +	struct device *dev = &host->pdev->dev;
> +	struct mmc_command *cmd = host->cmd;
> +	u32 sdcmd;
> +
> +	dev_dbg(dev, "finish_command(%x)\n", readl(host->ioaddr + SDCMD));
> +
> +	sdcmd = bcm2835_read_wait_sdcmd(host, 100, true);
> +
> +	/* Check for errors */
> +	if (sdcmd & SDCMD_NEW_FLAG) {
> +		dev_err(dev, "command never completed.\n");
> +		bcm2835_dumpregs(host);
> +		host->cmd->error = -EIO;
> +		bcm2835_finish_request(host);
> +		return;
> +	} else if (sdcmd & SDCMD_FAIL_FLAG) {
> +		u32 sdhsts = readl(host->ioaddr + SDHSTS);
> +
> +		/* Clear the errors */
> +		writel(SDHSTS_ERROR_MASK, host->ioaddr + SDHSTS);
> +
> +		if (!(sdhsts & SDHSTS_CRC7_ERROR) ||
> +		    (host->cmd->opcode != MMC_SEND_OP_COND)) {
> +			if (sdhsts & SDHSTS_CMD_TIME_OUT) {
> +				host->cmd->error = -ETIMEDOUT;
> +			} else {
> +				dev_err(dev, "unexpected command %d error\n",
> +					host->cmd->opcode);
> +				bcm2835_dumpregs(host);
> +				host->cmd->error = -EILSEQ;
> +			}
> +			bcm2835_finish_request(host);
> +			return;
> +		}
> +	}
> +
> +	if (cmd->flags & MMC_RSP_PRESENT) {
> +		if (cmd->flags & MMC_RSP_136) {
> +			int i;
> +
> +			for (i = 0; i < 4; i++) {
> +				cmd->resp[3 - i] =
> +					readl(host->ioaddr + SDRSP0 + i * 4);
> +			}
> +
> +			dev_dbg(dev, "finish_command %08x %08x %08x %08x\n",
> +				cmd->resp[0], cmd->resp[1],
> +				cmd->resp[2], cmd->resp[3]);
> +		} else {
> +			cmd->resp[0] = readl(host->ioaddr + SDRSP0);
> +			dev_dbg(dev, "finish_command %08x\n", cmd->resp[0]);
> +		}
> +	}
> +
> +	if (cmd == host->mrq->sbc) {
> +		/* Finished CMD23, now send actual command. */
> +		host->cmd = NULL;
> +		if (bcm2835_send_command(host, host->mrq->cmd)) {
> +			if (host->data && host->dma_desc)
> +				/* DMA transfer starts now, PIO starts
> +				 * after irq
> +				 */
> +				bcm2835_start_dma(host);
> +
> +			if (!host->use_busy)
> +				bcm2835_finish_command(host);

Recursion?

> +		}
> +	} else if (cmd == host->mrq->stop) {
> +		/* Finished CMD12 */
> +		bcm2835_finish_request(host);
> +	} else {
> +		/* Processed actual command. */
> +		host->cmd = NULL;
> +		if (!host->data)
> +			bcm2835_finish_request(host);
> +		else if (host->data_complete)
> +			bcm2835_transfer_complete(host);

bcm2835_finish_command() calls bcm2835_transfer_complete()
bcm2835_transfer_complete() calls bcm2835_finish_command()

This should be avoided.

> ...
> +int bcm2835_add_host(struct bcm2835_host *host)
> +{
> +	struct mmc_host *mmc = host->mmc;
> +	struct device *dev = &host->pdev->dev;
> +	struct dma_slave_config cfg;
> +	char pio_limit_string[20];
> +	int ret;
> +
> +	bcm2835_reset_internal(host);
> +
> +	mmc->f_max = host->max_clk;
> +	mmc->f_min = host->max_clk / SDCDIV_MAX_CDIV;
> +
> +	mmc->max_busy_timeout = ~0 / (mmc->f_max / 1000);
> +
> +	dev_dbg(dev, "f_max %d, f_min %d, max_busy_timeout %d\n",
> +		mmc->f_max, mmc->f_min, mmc->max_busy_timeout);
> +
> +	/* host controller capabilities */
> +	mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
> +		     MMC_CAP_NEEDS_POLL | MMC_CAP_HW_RESET | MMC_CAP_ERASE |
> +		     MMC_CAP_CMD23;
> +
> +	spin_lock_init(&host->lock);
> +	mutex_init(&host->mutex);
> +
> +	if (IS_ERR_OR_NULL(host->dma_chan_tx) ||
> +	    IS_ERR_OR_NULL(host->dma_chan_rx)) {
> +		dev_err(dev, "unable to initialise DMA channels. Falling back to PIO\n");
> +		host->use_dma = false;
> +	} else {
> +		host->use_dma = true;
> +
> +		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +		cfg.slave_id = 13;		/* DREQ channel */

Is this the same value which is also defined in the DT?

Thanks
Stefan
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux